Bug 926725 - Create DevToolsUtils.defineLazyPrototypeGetter and use it in VariablesView Scopes. r=fitzgen, r=vp
--- 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]