Bug 926725 - Create DevToolsUtils.defineLazyPrototypeGetter and use it in VariablesView Scopes. r=fitzgen, r=vp
authorBrandon Benvie <bbenvie@mozilla.com>
Tue, 15 Oct 2013 09:49:15 -0700
changeset 164741 97f6836ede99ce794564abd206471cd1c31a4d36
parent 164740 51185a26a7b7660550d6ab5e857459542d34b222
child 164742 63b7d4bbaab6aeafa152ffc7176f35e826595a51
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfitzgen, vp
bugs926725
milestone27.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 926725 - Create DevToolsUtils.defineLazyPrototypeGetter and use it in VariablesView Scopes. r=fitzgen, r=vp
browser/devtools/shared/widgets/VariablesView.jsm
toolkit/devtools/DevToolsUtils.js
toolkit/devtools/DevToolsUtils.jsm
toolkit/devtools/tests/unit/test_defineLazyPrototypeGetter.js
toolkit/devtools/tests/unit/xpcshell.ini
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -16,16 +16,17 @@ 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
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 Cu.import("resource:///modules/devtools/shared/event-emitter.js");
+Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
   "resource://gre/modules/devtools/Loader.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
   "@mozilla.org/widget/clipboardhelper;1",
   "nsIClipboardHelper");
 
@@ -1117,24 +1118,16 @@ function Scope(aView, aName, aFlags = {}
   this.preventDescriptorModifiers = aView.preventDescriptorModifiers;
   this.editableNameTooltip = aView.editableNameTooltip;
   this.editableValueTooltip = aView.editableValueTooltip;
   this.editButtonTooltip = aView.editButtonTooltip;
   this.deleteButtonTooltip = aView.deleteButtonTooltip;
   this.contextMenuId = aView.contextMenuId;
   this.separatorStr = aView.separatorStr;
 
-  // 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.
-  XPCOMUtils.defineLazyGetter(this, "_store", () => new Map());
-  XPCOMUtils.defineLazyGetter(this, "_enumItems", () => []);
-  XPCOMUtils.defineLazyGetter(this, "_nonEnumItems", () => []);
-  XPCOMUtils.defineLazyGetter(this, "_batchItems", () => []);
-
   this._init(aName.trim(), aFlags);
 }
 
 Scope.prototype = {
   /**
    * Whether this Scope should be prefetched when it is remoted.
    */
   shouldPrefetch: true,
@@ -2034,16 +2027,24 @@ Scope.prototype = {
   _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] in instance) { }".
  *
  * @param Scope aScope
  *        The scope to contain this variable.
  * @param string aName
  *        The variable's name.
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -129,8 +129,40 @@ this.yieldingEach = function yieldingEac
       }
     }
 
     deferred.resolve();
   }());
 
   return deferred.promise;
 }
+
+
+/**
+ * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
+ * allows the lazy getter to be defined on a prototype and work correctly with
+ * instances.
+ *
+ * @param Object aObject
+ *        The prototype object to define the lazy getter on.
+ * @param String aKey
+ *        The key to define the lazy getter on.
+ * @param Function aCallback
+ *        The callback that will be called to determine the value. Will be
+ *        called with the |this| value of the current instance.
+ */
+this.defineLazyPrototypeGetter =
+function defineLazyPrototypeGetter(aObject, aKey, aCallback) {
+  Object.defineProperty(aObject, aKey, {
+    configurable: true,
+    get: function() {
+      const value = aCallback.call(this);
+
+      Object.defineProperty(this, aKey, {
+        configurable: true,
+        writable: true,
+        value: value
+      });
+
+      return value;
+    }
+  });
+}
--- a/toolkit/devtools/DevToolsUtils.jsm
+++ b/toolkit/devtools/DevToolsUtils.jsm
@@ -17,10 +17,11 @@ this.EXPORTED_SYMBOLS = [ "DevToolsUtils
 Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Components.interfaces.mozIJSSubScriptLoader)
   .loadSubScript("resource://gre/modules/devtools/DevToolsUtils.js", this);
 
 this.DevToolsUtils = {
   safeErrorString: safeErrorString,
   reportException: reportException,
   makeInfallible: makeInfallible,
-  yieldingEach: yieldingEach
+  yieldingEach: yieldingEach,
+  defineLazyPrototypeGetter: defineLazyPrototypeGetter
 };
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/tests/unit/test_defineLazyPrototypeGetter.js
@@ -0,0 +1,68 @@
+/* -*- Mode: js; js-indent-level: 2; -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test DevToolsUtils.defineLazyPrototypeGetter
+
+function Class() {}
+DevToolsUtils.defineLazyPrototypeGetter(Class.prototype, "foo", () => []);
+
+
+function run_test() {
+  test_prototype_attributes();
+  test_instance_attributes();
+  test_multiple_instances();
+  test_callback_receiver();
+}
+
+function test_prototype_attributes() {
+  // Check that the prototype has a getter property with expected attributes.
+  let descriptor = Object.getOwnPropertyDescriptor(Class.prototype, "foo");
+  do_check_eq(typeof descriptor.get, "function");
+  do_check_eq(descriptor.set, undefined);
+  do_check_eq(descriptor.enumerable, false);
+  do_check_eq(descriptor.configurable, true);
+}
+
+function test_instance_attributes() {
+  // Instances should not have an own property until the lazy getter has been
+  // activated.
+  let instance = new Class();
+  do_check_false(instance.hasOwnProperty("foo"));
+  instance.foo;
+  do_check_true(instance.hasOwnProperty("foo"));
+
+  // Check that the instance has an own property with the expecred value and
+  // attributes after the lazy getter is activated.
+  let descriptor = Object.getOwnPropertyDescriptor(instance, "foo");
+  do_check_true(descriptor.value instanceof Array);
+  do_check_eq(descriptor.writable, true);
+  do_check_eq(descriptor.enumerable, false);
+  do_check_eq(descriptor.configurable, true);
+}
+
+function test_multiple_instances() {
+  let instance1 = new Class();
+  let instance2 = new Class();
+  let foo1 = instance1.foo;
+  let foo2 = instance2.foo;
+  // Check that the lazy getter returns the expected type of value.
+  do_check_true(foo1 instanceof Array);
+  do_check_true(foo2 instanceof Array);
+  // Make sure the lazy getter runs once and only once per instance.
+  do_check_eq(instance1.foo, foo1);
+  do_check_eq(instance2.foo, foo2);
+  // Make sure each instance gets its own unique value.
+  do_check_neq(foo1, foo2);
+}
+
+function test_callback_receiver() {
+  function Foo() {};
+  DevToolsUtils.defineLazyPrototypeGetter(Foo.prototype, "foo", function() {
+    return this;
+  });
+
+  // Check that the |this| value in the callback is the instance itself.
+  let instance = new Foo();
+  do_check_eq(instance.foo, instance);
+}
--- a/toolkit/devtools/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/tests/unit/xpcshell.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 head = head_devtools.js
 tail =
 
 [test_safeErrorString.js]
+[test_defineLazyPrototypeGetter.js]