Bug 830344 - Part 1: Remove the lazy append mechanism in the variables view, r=past
authorVictor Porof <vporof@mozilla.com>
Wed, 18 Dec 2013 19:01:38 +0200
changeset 161088 e472c39cc2bbe9fa5391bf74b5905614228a613a
parent 161087 8ac6db4d1199c3602387b4732a1ea3c207c05aea
child 161089 b48f948547efc7b04bb6d111935a19c18d83a268
push id25867
push userryanvm@gmail.com
push dateThu, 19 Dec 2013 02:19:33 +0000
treeherdermozilla-central@04a70c8908de [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspast
bugs830344
milestone29.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 830344 - Part 1: Remove the lazy append mechanism in the variables view, r=past
browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js
browser/devtools/debugger/test/head.js
browser/devtools/shared/widgets/VariablesView.jsm
browser/devtools/webconsole/test/head.js
browser/devtools/webconsole/webconsole.js
--- a/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js
+++ b/browser/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js
@@ -7,224 +7,38 @@
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_large-array-buffer.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
 let gVariables;
 
 function test() {
-  // This is a very, very stressful test.
-  // Thankfully, after bug 830344 none of this will be necessary anymore.
-  requestLongerTimeout(10);
-
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gVariables = gDebugger.DebuggerView.Variables;
 
-    gDebugger.DebuggerView.Variables.lazyAppend = true;
-
     waitForSourceAndCaretAndScopes(gPanel, ".html", 18)
       .then(() => performTest())
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     EventUtils.sendMouseEvent({ type: "click" },
       gDebuggee.document.querySelector("button"),
       gDebuggee);
   });
 }
 
 function performTest() {
-  let deferred = promise.defer();
 
-  let localScope = gVariables.getScopeAtIndex(0);
-  is(localScope.expanded, true,
-    "The local scope should be expanded by default.");
-
-  let localEnums = localScope.target.querySelector(".variables-view-element-details.enum").childNodes;
-  let localNonEnums = localScope.target.querySelector(".variables-view-element-details.nonenum").childNodes;
-
-  is(localEnums.length, 5,
-    "The local scope should contain all the created enumerable elements.");
-  is(localNonEnums.length, 0,
-    "The local scope should contain all the created non-enumerable elements.");
-
-  let bufferVar = localScope.get("buffer");
-  let zVar = localScope.get("z");
-
-  is(bufferVar.target.querySelector(".name").getAttribute("value"), "buffer",
-    "Should have the right property name for 'buffer'.");
-  is(bufferVar.target.querySelector(".value").getAttribute("value"), "ArrayBuffer",
-    "Should have the right property value for 'buffer'.");
-  ok(bufferVar.target.querySelector(".value").className.contains("token-other"),
-    "Should have the right token class for 'buffer'.");
-
-  is(zVar.target.querySelector(".name").getAttribute("value"), "z",
-    "Should have the right property name for 'z'.");
-  is(zVar.target.querySelector(".value").getAttribute("value"), "Int8Array",
-    "Should have the right property value for 'z'.");
-  ok(zVar.target.querySelector(".value").className.contains("token-other"),
-    "Should have the right token class for 'z'.");
-
-  EventUtils.sendMouseEvent({ type: "mousedown" },
-    bufferVar.target.querySelector(".arrow"),
-    gDebugger);
-
-  EventUtils.sendMouseEvent({ type: "mousedown" },
-    zVar.target.querySelector(".arrow"),
-    gDebugger);
-
-  // Need to wait for 0 enumerable and 2 non-enumerable properties in bufferVar,
-  // and 10000 enumerable and 5 non-enumerable properties in zVar.
-  let total = 0 + 2 + 10000 + 5;
-  let loaded = 0;
-  let paints = 0;
-
-  // Make sure the variables view doesn't scroll while adding the properties.
-  let [oldX, oldY] = getScroll();
-  info("Initial scroll position: " + oldX + ", " + oldY);
-
-  waitForProperties(total, {
-    onLoading: function(aLoaded) {
-      ok(aLoaded >= loaded,
-        "Should have loaded more properties.");
-
-      let [newX, newY] = getScroll();
-      info("Current scroll position: " + newX + " " + newY);
-      is(oldX, newX, "The variables view hasn't scrolled horizontally.");
-      is(oldY, newY, "The variables view hasn't scrolled vertically.");
-
-      info("Displayed " + aLoaded + " properties, not finished yet.");
-
-      loaded = aLoaded;
-      paints++;
-    },
-    onFinished: function(aLoaded) {
-      ok(aLoaded == total,
-        "Displayed all the properties.");
-      isnot(paints, 0,
-        "Debugger was unresponsive, sad panda.");
-
-      let [newX, newY] = getScroll();
-      info("Current scroll position: " + newX + ", " + newY);
-      is(oldX, newX, "The variables view hasn't scrolled horizontally.");
-      is(oldY, newY, "The variables view hasn't scrolled vertically.");
-
-      is(bufferVar._enum.childNodes.length, 0,
-        "The bufferVar should contain all the created enumerable elements.");
-      is(bufferVar._nonenum.childNodes.length, 2,
-        "The bufferVar should contain all the created non-enumerable elements.");
-
-      let bufferVarByteLengthProp = bufferVar.get("byteLength");
-      let bufferVarProtoProp = bufferVar.get("__proto__");
-
-      is(bufferVarByteLengthProp.target.querySelector(".name").getAttribute("value"), "byteLength",
-        "Should have the right property name for 'byteLength'.");
-      is(bufferVarByteLengthProp.target.querySelector(".value").getAttribute("value"), "10000",
-        "Should have the right property value for 'byteLength'.");
-      ok(bufferVarByteLengthProp.target.querySelector(".value").className.contains("token-number"),
-        "Should have the right token class for 'byteLength'.");
-
-      is(bufferVarProtoProp.target.querySelector(".name").getAttribute("value"), "__proto__",
-        "Should have the right property name for '__proto__'.");
-      is(bufferVarProtoProp.target.querySelector(".value").getAttribute("value"), "ArrayBufferPrototype",
-        "Should have the right property value for '__proto__'.");
-      ok(bufferVarProtoProp.target.querySelector(".value").className.contains("token-other"),
-        "Should have the right token class for '__proto__'.");
-
-      is(zVar._enum.childNodes.length, 10000,
-        "The zVar should contain all the created enumerable elements.");
-      is(zVar._nonenum.childNodes.length, 5,
-        "The zVar should contain all the created non-enumerable elements.");
-
-      let zVarByteLengthProp = zVar.get("byteLength");
-      let zVarByteOffsetProp = zVar.get("byteOffset");
-      let zVarProtoProp = zVar.get("__proto__");
-
-      is(zVarByteLengthProp.target.querySelector(".name").getAttribute("value"), "byteLength",
-        "Should have the right property name for 'byteLength'.");
-      is(zVarByteLengthProp.target.querySelector(".value").getAttribute("value"), "10000",
-        "Should have the right property value for 'byteLength'.");
-      ok(zVarByteLengthProp.target.querySelector(".value").className.contains("token-number"),
-        "Should have the right token class for 'byteLength'.");
-
-      is(zVarByteOffsetProp.target.querySelector(".name").getAttribute("value"), "byteOffset",
-        "Should have the right property name for 'byteOffset'.");
-      is(zVarByteOffsetProp.target.querySelector(".value").getAttribute("value"), "0",
-        "Should have the right property value for 'byteOffset'.");
-      ok(zVarByteOffsetProp.target.querySelector(".value").className.contains("token-number"),
-        "Should have the right token class for 'byteOffset'.");
-
-      is(zVarProtoProp.target.querySelector(".name").getAttribute("value"), "__proto__",
-        "Should have the right property name for '__proto__'.");
-      is(zVarProtoProp.target.querySelector(".value").getAttribute("value"), "Int8ArrayPrototype",
-        "Should have the right property value for '__proto__'.");
-      ok(zVarProtoProp.target.querySelector(".value").className.contains("token-other"),
-        "Should have the right token class for '__proto__'.");
-
-      let arrayElements = zVar._enum.childNodes;
-      for (let i = 0, len = arrayElements.length; i < len; i++) {
-        let node = arrayElements[i];
-        let name = node.querySelector(".name").getAttribute("value");
-        let value = node.querySelector(".value").getAttribute("value");
-        if (name !== i + "" || value !== "0") {
-          ok(false, "The array items aren't in the correct order.");
-        }
-      }
-
-      deferred.resolve();
-    },
-    onTimeout: function() {
-      ok(false, "Timed out while polling for the properties.");
-      deferred.resolve();
-    }
-  });
-
-  function getScroll() {
-    let scrollX = {};
-    let scrollY = {};
-    gVariables.boxObject.getPosition(scrollX, scrollY);
-    return [scrollX.value, scrollY.value];
-  }
-
-  return deferred.promise;
-}
-
-function waitForProperties(aTotal, aCallbacks, aInterval = 10) {
-  let localScope = gVariables.getScopeAtIndex(0);
-  let bufferEnum = localScope.get("buffer")._enum.childNodes;
-  let bufferNonEnum = localScope.get("buffer")._nonenum.childNodes;
-  let zEnum = localScope.get("z")._enum.childNodes;
-  let zNonEnum = localScope.get("z")._nonenum.childNodes;
-
-  // Poll every few milliseconds until the properties are retrieved.
-  let count = 0;
-  let intervalId = window.setInterval(() => {
-    // Make sure we don't wait for too long.
-    if (++count > 1000) {
-      window.clearInterval(intervalId);
-      aCallbacks.onTimeout();
-      return;
-    }
-    // Check if we need to wait for a few more properties to be fetched.
-    let loaded = bufferEnum.length + bufferNonEnum.length + zEnum.length + zNonEnum.length;
-    if (loaded < aTotal) {
-      aCallbacks.onLoading(loaded);
-      return;
-    }
-    // We got all the properties, it's safe to callback.
-    window.clearInterval(intervalId);
-    aCallbacks.onFinished(loaded);
-  }, aInterval);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gVariables = null;
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -483,18 +483,16 @@ function initChromeDebugger(aOnClose) {
 
   return deferred.promise;
 }
 
 function prepareDebugger(aDebugger) {
   if ("target" in aDebugger) {
     let variables = aDebugger.panelWin.DebuggerView.Variables;
     variables.lazyEmpty = false;
-    variables.lazyAppend = false;
-    variables.lazyExpand = false;
     variables.lazySearch = false;
   } else {
     // Nothing to do here yet.
   }
 }
 
 function teardown(aPanel, aFlags = {}) {
   info("Destroying the specified debugger.");
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -6,18 +6,16 @@
 "use strict";
 
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const LAZY_EMPTY_DELAY = 150; // ms
 const LAZY_EXPAND_DELAY = 50; // ms
-const LAZY_APPEND_DELAY = 100; // ms
-const LAZY_APPEND_BATCH = 100; // nodes
 const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100;
 const PAGE_SIZE_MAX_JUMPS = 30;
 const SEARCH_ACTION_MAX_DELAY = 300; // ms
 const ITEM_FLASH_DURATION = 300 // ms
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
@@ -221,28 +219,16 @@ VariablesView.prototype = {
 
   /**
    * Specifies if this view may be emptied lazily.
    * @see VariablesView.prototype.empty
    */
   lazyEmpty: false,
 
   /**
-   * Specifies if nodes in this view may be added lazily.
-   * @see Scope.prototype._lazyAppend
-   */
-  lazyAppend: true,
-
-  /**
-   * Specifies if nodes in this view may be expanded lazily.
-   * @see Scope.prototype.expand
-   */
-  lazyExpand: true,
-
-  /**
    * Specifies if nodes in this view may be searched lazily.
    */
   lazySearch: true,
 
   /**
    * Function called each time a variable or property's value is changed via
    * user interaction. If null, then value changes are disabled.
    *
@@ -1196,17 +1182,16 @@ VariablesView.getterOrSetterDeleteCallba
  *        Additional options or flags for this scope.
  */
 function Scope(aView, aName, aFlags = {}) {
   this.ownerView = aView;
 
   this._onClick = this._onClick.bind(this);
   this._openEnum = this._openEnum.bind(this);
   this._openNonEnum = this._openNonEnum.bind(this);
-  this._batchAppend = this._batchAppend.bind(this);
 
   // Inherit properties and flags from the parent view. You can override
   // each of these directly onto any scope, variable or property instance.
   this.eval = aView.eval;
   this.switch = aView.switch;
   this.delete = aView.delete;
   this.new = aView.new;
   this.preventDisableOnChange = aView.preventDisableOnChange;
@@ -1223,16 +1208,21 @@ function Scope(aView, aName, aFlags = {}
 
 Scope.prototype = {
   /**
    * Whether this Scope should be prefetched when it is remoted.
    */
   shouldPrefetch: true,
 
   /**
+   * The class name applied to this scope's target element.
+   */
+  targetClassName: "variables-view-scope",
+
+  /**
    * Create a new Variable that is a child of this Scope.
    *
    * @param string aName
    *        The name of the new Property.
    * @param object aDescriptor
    *        The variable's descriptor.
    * @return Variable
    *         The newly created child Variable.
@@ -1254,18 +1244,19 @@ Scope.prototype = {
    *        e.g. - { value: 42 }
    *             - { value: true }
    *             - { value: "nasu" }
    *             - { value: { type: "undefined" } }
    *             - { value: { type: "null" } }
    *             - { value: { type: "object", class: "Object" } }
    *             - { get: { type: "object", class: "Function" },
    *                 set: { type: "undefined" } }
-   * @param boolean aRelaxed
-   *        True if name duplicates should be allowed.
+   * @param boolean aRelaxed [optional]
+   *        Pass true if name duplicates should be allowed.
+   *        You probably shouldn't do it. Use this with caution.
    * @return Variable
    *         The newly created Variable instance, null if it already exists.
    */
   addItem: function(aName = "", aDescriptor = {}, aRelaxed = false) {
     if (this._store.has(aName) && !aRelaxed) {
       return null;
     }
 
@@ -1427,54 +1418,37 @@ Scope.prototype = {
       this.onhide(this);
     }
   },
 
   /**
    * Expands the scope, showing all the added details.
    */
   expand: function() {
-    if (this._isExpanded || this._locked) {
+    if (this._isExpanded || this._isLocked) {
       return;
     }
-    // If there's a large number of enumerable or non-enumerable items
-    // contained in this scope, painting them may take several seconds,
-    // even if they were already displayed before. In this case, show a throbber
-    // to suggest that this scope is expanding.
-    if (!this._isExpanding &&
-         this._variablesView.lazyExpand &&
-         this._store.size > LAZY_APPEND_BATCH) {
-      this._isExpanding = true;
-
-      // Start spinning a throbber in this scope's title and allow a few
-      // milliseconds for it to be painted.
-      this._startThrobber();
-      this.window.setTimeout(this.expand.bind(this), LAZY_EXPAND_DELAY);
-      return;
-    }
-
     if (this._variablesView._enumVisible) {
       this._openEnum();
     }
     if (this._variablesView._nonEnumVisible) {
       Services.tm.currentThread.dispatch({ run: this._openNonEnum }, 0);
     }
-    this._isExpanding = false;
     this._isExpanded = true;
 
     if (this.onexpand) {
       this.onexpand(this);
     }
   },
 
   /**
    * Collapses the scope, hiding all the added details.
    */
   collapse: function() {
-    if (!this._isExpanded || this._locked) {
+    if (!this._isExpanded || this._isLocked) {
       return;
     }
     this._arrow.removeAttribute("open");
     this._enum.removeAttribute("open");
     this._nonenum.removeAttribute("open");
     this._isExpanded = false;
 
     if (this.oncollapse) {
@@ -1571,17 +1545,17 @@ Scope.prototype = {
    * @return boolean
    */
   get twisty() this._isArrowVisible,
 
   /**
    * Gets the expand lock state.
    * @return boolean
    */
-  get locked() this._locked,
+  get locked() this._isLocked,
 
   /**
    * Sets the visibility state.
    * @param boolean aFlag
    */
   set visible(aFlag) aFlag ? this.show() : this.hide(),
 
   /**
@@ -1601,17 +1575,17 @@ Scope.prototype = {
    * @param boolean aFlag
    */
   set twisty(aFlag) aFlag ? this.showArrow() : this.hideArrow(),
 
   /**
    * Sets the expand lock state.
    * @param boolean aFlag
    */
-  set locked(aFlag) this._locked = aFlag,
+  set locked(aFlag) this._isLocked = aFlag,
 
   /**
    * Specifies if this target node may be focused.
    * @return boolean
    */
   get focusable() {
     // Check if this target node is actually visibile.
     if (!this._nameString ||
@@ -1694,47 +1668,47 @@ Scope.prototype = {
    *
    * @param string aName
    *        The scope's name.
    * @param object aFlags [optional]
    *        Additional options or flags for this scope.
    */
   _init: function(aName, aFlags) {
     this._idString = generateId(this._nameString = aName);
-    this._displayScope(aName, "variables-view-scope", "devtools-toolbar");
+    this._displayScope(aName, this.targetClassName, "devtools-toolbar");
     this._addEventListeners();
     this.parentNode.appendChild(this._target);
   },
 
   /**
    * Creates the necessary nodes for this scope.
    *
    * @param string aName
    *        The scope's name.
-   * @param string aClassName
-   *        A custom class name for this scope.
+   * @param string aTargetClassName
+   *        A custom class name for this scope's target element.
    * @param string aTitleClassName [optional]
-   *        A custom class name for this scope's title.
+   *        A custom class name for this scope's title element.
    */
-  _displayScope: function(aName, aClassName, aTitleClassName) {
+  _displayScope: function(aName, aTargetClassName, aTitleClassName = "") {
     let document = this.document;
 
     let element = this._target = document.createElement("vbox");
     element.id = this._idString;
-    element.className = aClassName;
+    element.className = aTargetClassName;
 
     let arrow = this._arrow = document.createElement("hbox");
     arrow.className = "arrow";
 
     let name = this._name = document.createElement("label");
     name.className = "plain name";
     name.setAttribute("value", aName);
 
     let title = this._title = document.createElement("hbox");
-    title.className = "title " + (aTitleClassName || "");
+    title.className = "title " + aTitleClassName;
     title.setAttribute("align", "center");
 
     let enumerable = this._enum = document.createElement("vbox");
     let nonenum = this._nonenum = document.createElement("vbox");
     enumerable.className = "variables-view-element-details enum";
     nonenum.className = "variables-view-element-details nonenum";
 
     title.appendChild(arrow);
@@ -1762,116 +1736,28 @@ Scope.prototype = {
         e.target == this._addPropertyNode) {
       return;
     }
     this.toggle();
     this.focus();
   },
 
   /**
-   * Lazily appends a node to this scope's enumerable or non-enumerable
-   * container. Once a certain number of nodes have been batched, they
-   * will be appended.
-   *
-   * @param boolean aImmediateFlag
-   *        Set to false if append calls should be dispatched synchronously
-   *        on the current thread, to allow for a paint flush.
-   * @param boolean aEnumerableFlag
-   *        Specifies if the node to append is enumerable or non-enumerable.
-   * @param nsIDOMNode aChild
-   *        The child node to append.
-   */
-  _lazyAppend: function(aImmediateFlag, aEnumerableFlag, aChild) {
-    // Append immediately, don't stage items and don't allow for a paint flush.
-    if (aImmediateFlag || !this._variablesView.lazyAppend) {
-      if (aEnumerableFlag) {
-        this._enum.appendChild(aChild);
-      } else {
-        this._nonenum.appendChild(aChild);
-      }
-      return;
-    }
-
-    let window = this.window;
-    let batchItems = this._batchItems;
-
-    window.clearTimeout(this._batchTimeout);
-    batchItems.push({ enumerableFlag: aEnumerableFlag, child: aChild });
-
-    // If a certain number of nodes have been batched, append all the
-    // staged items now.
-    if (batchItems.length > LAZY_APPEND_BATCH) {
-      // Allow for a paint flush.
-      Services.tm.currentThread.dispatch({ run: this._batchAppend }, 1);
-      return;
-    }
-    // Postpone appending the staged items for later, to allow batching
-    // more nodes.
-    this._batchTimeout = window.setTimeout(this._batchAppend, LAZY_APPEND_DELAY);
-  },
-
-  /**
-   * Appends all the batched nodes to this scope's enumerable and non-enumerable
-   * containers.
-   */
-  _batchAppend: function() {
-    let document = this.document;
-    let batchItems = this._batchItems;
-
-    // Create two document fragments, one for enumerable nodes, and one
-    // for non-enumerable nodes.
-    let frags = [document.createDocumentFragment(), document.createDocumentFragment()];
-
-    for (let item of batchItems) {
-      frags[~~item.enumerableFlag].appendChild(item.child);
-    }
-    batchItems.length = 0;
-    this._enum.appendChild(frags[1]);
-    this._nonenum.appendChild(frags[0]);
-  },
-
-  /**
-   * Starts spinning a throbber in this scope's title.
-   */
-  _startThrobber: function() {
-    if (this._throbber) {
-      this._throbber.hidden = false;
-      return;
-    }
-    let throbber = this._throbber = this.document.createElement("hbox");
-    throbber.className = "variables-view-throbber";
-    throbber.setAttribute("optional-visibility", "");
-    this._title.insertBefore(throbber, this._spacer);
-  },
-
-  /**
-   * Stops spinning the throbber in this scope's title.
-   */
-  _stopThrobber: function() {
-    if (!this._throbber) {
-      return;
-    }
-    this._throbber.hidden = true;
-  },
-
-  /**
    * Opens the enumerable items container.
    */
   _openEnum: function() {
     this._arrow.setAttribute("open", "");
     this._enum.setAttribute("open", "");
-    this._stopThrobber();
   },
 
   /**
    * Opens the non-enumerable items container.
    */
   _openNonEnum: function() {
     this._nonenum.setAttribute("open", "");
-    this._stopThrobber();
   },
 
   /**
    * Specifies if enumerable properties and variables should be displayed.
    * @param boolean aFlag
    */
   set _enumVisible(aFlag) {
     for (let [, variable] of this._store) {
@@ -2103,43 +1989,38 @@ Scope.prototype = {
   separatorStr: "",
 
   _store: null,
   _enumItems: null,
   _nonEnumItems: null,
   _fetched: false,
   _retrieved: false,
   _committed: false,
-  _batchItems: null,
-  _batchTimeout: null,
-  _locked: false,
-  _isExpanding: false,
+  _isLocked: false,
   _isExpanded: false,
   _isContentVisible: true,
   _isHeaderVisible: true,
   _isArrowVisible: true,
   _isMatch: true,
   _idString: "",
   _nameString: "",
   _target: null,
   _arrow: null,
   _name: null,
   _title: null,
   _enum: null,
   _nonenum: null,
-  _throbber: null
 };
 
 // Creating maps and arrays thousands of times for variables or properties
 // with a large number of children fills up a lot of memory. Make sure
 // these are instantiated only if needed.
 DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_store", Map);
 DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_enumItems", Array);
 DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_nonEnumItems", Array);
-DevToolsUtils.defineLazyPrototypeGetter(Scope.prototype, "_batchItems", Array);
 
 /**
  * A Variable is a Scope holding Property instances.
  * Iterable via "for (let [name, property] of instance) { }".
  *
  * @param Scope aScope
  *        The scope to contain this variable.
  * @param string aName
@@ -2169,16 +2050,21 @@ Variable.prototype = Heritage.extend(Sco
   /**
    * Whether this Scope should be prefetched when it is remoted.
    */
   get shouldPrefetch(){
     return this.name == "window" || this.name == "this";
   },
 
   /**
+   * The class name applied to this variable's target element.
+   */
+  targetClassName: "variables-view-variable variable-or-property",
+
+  /**
    * Create a new Property that is a child of Variable.
    *
    * @param string aName
    *        The name of the new Property.
    * @param object aDescriptor
    *        The property's descriptor.
    * @return Property
    *         The newly created child Property.
@@ -2410,43 +2296,31 @@ Variable.prototype = Heritage.extend(Sco
    *
    * @param string aName
    *        The variable's name.
    * @param object aDescriptor
    *        The variable's descriptor.
    */
   _init: function(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
-    this._displayScope(aName, "variables-view-variable variable-or-property");
-
+    this._displayScope(aName, this.targetClassName);
     this._displayVariable();
     this._customizeVariable();
     this._prepareTooltips();
     this._setAttributes();
     this._addEventListeners();
 
-    this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH);
-  },
-
-  /**
-   * Called when this variable has finished initializing, and is ready to
-   * be attached to the owner view.
-   *
-   * @param boolean aImmediateFlag
-   *        @see Scope.prototype._lazyAppend
-   */
-  _onInit: function(aImmediateFlag) {
     if (this._initialDescriptor.enumerable ||
         this._nameString == "this" ||
         this._nameString == "<return>" ||
         this._nameString == "<exception>") {
-      this.ownerView._lazyAppend(aImmediateFlag, true, this._target);
+      this.ownerView._enum.appendChild(this._target);
       this.ownerView._enumItems.push(this);
     } else {
-      this.ownerView._lazyAppend(aImmediateFlag, false, this._target);
+      this.ownerView._nonenum.appendChild(this._target);
       this.ownerView._nonEnumItems.push(this);
     }
   },
 
   /**
    * Creates the necessary nodes for this variable.
    */
   _displayVariable: function() {
@@ -2863,52 +2737,19 @@ Variable.prototype = Heritage.extend(Sco
 function Property(aVar, aName, aDescriptor) {
   Variable.call(this, aVar, aName, aDescriptor);
   this._symbolicName = aVar._symbolicName + "[\"" + aName + "\"]";
   this._absoluteName = aVar._absoluteName + "[\"" + aName + "\"]";
 }
 
 Property.prototype = Heritage.extend(Variable.prototype, {
   /**
-   * Initializes this property's id, view and binds event listeners.
-   *
-   * @param string aName
-   *        The property's name.
-   * @param object aDescriptor
-   *        The property's descriptor.
+   * The class name applied to this property's target element.
    */
-  _init: function(aName = "", aDescriptor) {
-    this._idString = generateId(this._nameString = aName);
-    this._displayScope(aName, "variables-view-property variable-or-property");
-
-    this._displayVariable();
-    this._customizeVariable();
-    this._prepareTooltips();
-    this._setAttributes();
-    this._addEventListeners();
-
-    this._onInit(this.ownerView._store.size < LAZY_APPEND_BATCH);
-  },
-
-  /**
-   * Called when this property has finished initializing, and is ready to
-   * be attached to the owner view.
-   *
-   * @param boolean aImmediateFlag
-   *        @see Scope.prototype._lazyAppend
-   */
-  _onInit: function(aImmediateFlag) {
-    if (this._initialDescriptor.enumerable) {
-      this.ownerView._lazyAppend(aImmediateFlag, true, this._target);
-      this.ownerView._enumItems.push(this);
-    } else {
-      this.ownerView._lazyAppend(aImmediateFlag, false, this._target);
-      this.ownerView._nonEnumItems.push(this);
-    }
-  }
+  targetClassName: "variables-view-property variable-or-property"
 });
 
 /**
  * A generator-iterator over the VariablesView, Scopes, Variables and Properties.
  */
 VariablesView.prototype["@@iterator"] =
 Scope.prototype["@@iterator"] =
 Variable.prototype["@@iterator"] =
@@ -3360,17 +3201,16 @@ Editable.prototype = {
     input.addEventListener("keypress", this._onKeypress);
     input.addEventListener("blur", this._onBlur);
 
     this._prevExpandable = this._variable.twisty;
     this._prevExpanded = this._variable.expanded;
     this._variable.collapse();
     this._variable.hideArrow();
     this._variable.locked = true;
-    this._variable._stopThrobber();
   },
 
   /**
    * Remove the input box and restore the Variable or Property to its previous
    * state.
    */
   deactivate: function() {
     this._input.removeEventListener("keypress", this._onKeypress);
@@ -3378,17 +3218,16 @@ Editable.prototype = {
     this._input.parentNode.replaceChild(this.label, this._input);
     this._input = null;
 
     let { boxObject } = this._variable._variablesView;
     boxObject.scrollBy(-this._variable._target, 0);
     this._variable.locked = false;
     this._variable.twisty = this._prevExpandable;
     this._variable.expanded = this._prevExpanded;
-    this._variable._stopThrobber();
   },
 
   /**
    * Save the current value and deactivate the Editable.
    */
   _save: function() {
     let initial = this.label.getAttribute("value");
     let current = this._input.value.trim();
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -791,17 +791,16 @@ function openDebugger(aOptions = {})
   let toolbox = gDevTools.getToolbox(target);
   let dbgPanelAlreadyOpen = toolbox.getPanel("jsdebugger");
 
   gDevTools.showToolbox(target, "jsdebugger").then(function onSuccess(aToolbox) {
     let panel = aToolbox.getCurrentPanel();
     let panelWin = panel.panelWin;
 
     panel._view.Variables.lazyEmpty = false;
-    panel._view.Variables.lazyAppend = false;
 
     let resolveObject = {
       target: target,
       toolbox: aToolbox,
       panel: panel,
       panelWin: panelWin,
     };
 
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -3426,17 +3426,16 @@ JSTerm.prototype = {
    */
   _createVariablesView: function JST__createVariablesView(aOptions)
   {
     let view = new VariablesView(aOptions.container);
     view.searchPlaceholder = l10n.getStr("propertiesFilterPlaceholder");
     view.emptyText = l10n.getStr("emptyPropertiesList");
     view.searchEnabled = !aOptions.hideFilterInput;
     view.lazyEmpty = this._lazyVariablesView;
-    view.lazyAppend = this._lazyVariablesView;
 
     VariablesViewController.attach(view, {
       getEnvironmentClient: aGrip => {
         return new EnvironmentClient(this.hud.proxy.client, aGrip);
       },
       getObjectClient: aGrip => {
         return new ObjectClient(this.hud.proxy.client, aGrip);
       },