Bug 1464548: Part 2b - Don't delete properties before redefining them, because deleting properties kills JIT performance. r?mccr8 draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 25 May 2018 19:17:58 -0700
changeset 800181 b2e725c1d21f1cbbdec5d78f13b81c1aa730c1d0
parent 800180 78e8b515a3bcc6b41b3d32e972c69c11ac0e08ce
child 800182 4897eb7a9541c063f07c96b21fd2c22f7e03a93e
push id111294
push usermaglione.k@gmail.com
push dateSat, 26 May 2018 05:34:03 +0000
reviewersmccr8
bugs1464548
milestone62.0a1
Bug 1464548: Part 2b - Don't delete properties before redefining them, because deleting properties kills JIT performance. r?mccr8 MozReview-Commit-ID: IUMg59xRoIu
js/xpconnect/loader/XPCOMUtils.jsm
--- a/js/xpconnect/loader/XPCOMUtils.jsm
+++ b/js/xpconnect/loader/XPCOMUtils.jsm
@@ -87,16 +87,31 @@
  *  this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
  */
 
 
 var EXPORTED_SYMBOLS = [ "XPCOMUtils" ];
 
 let global = Cu.getGlobalForObject({});
 
+/**
+ * Redefines the given property on the given object with the given
+ * value. This can be used to redefine getter properties which do not
+ * implement setters.
+ */
+function redefine(object, prop, value) {
+  Object.defineProperty(object, prop, {
+    configurable: true,
+    enumerable: true,
+    value,
+    writable: true,
+  });
+  return value;
+}
+
 var XPCOMUtils = {
   /**
    * Generate a QueryInterface implementation. The returned function must be
    * assigned to the 'QueryInterface' property of a JS object. When invoked on
    * that object, it checks if the given iid is listed in the |interfaces|
    * param, and if it is, returns |this| (the object it was called on).
    * If the JS object has a classInfo property it'll be returned for the
    * nsIClassInfo IID, generateCI can be used to generate the classInfo
@@ -170,31 +185,25 @@ var XPCOMUtils = {
    * @param aName
    *        The name of the getter to define on aObject.
    * @param aLambda
    *        A function that returns what the getter should return.  This will
    *        only ever be called once.
    */
   defineLazyGetter: function XPCU_defineLazyGetter(aObject, aName, aLambda)
   {
+    let redefining = false;
     Object.defineProperty(aObject, aName, {
       get: function () {
-        // Redefine this accessor property as a data property.
-        // Delete it first, to rule out "too much recursion" in case aObject is
-        // a proxy whose defineProperty handler might unwittingly trigger this
-        // getter again.
-        delete aObject[aName];
-        let value = aLambda.apply(aObject);
-        Object.defineProperty(aObject, aName, {
-          value,
-          writable: true,
-          configurable: true,
-          enumerable: true
-        });
-        return value;
+        if (!redefining) {
+          // Make sure we don't get into an infinite recursion loop if
+          // the getter lambda does something shady.
+          redefining = true;
+          return redefine(aObject, aName, aLambda.apply(aObject));
+        }
       },
       configurable: true,
       enumerable: true
     });
   },
 
   /**
    * Defines a getter on a specified object for a script.  The script will not
@@ -214,22 +223,22 @@ var XPCOMUtils = {
                                                                aResource)
   {
     if (!Array.isArray(aNames)) {
       aNames = [aNames];
     }
     for (let name of aNames) {
       Object.defineProperty(aObject, name, {
         get: function() {
-          for (let n of aNames) {
-            delete aObject[n];
-          }
           Services.scriptloader.loadSubScript(aResource, aObject);
           return aObject[name];
         },
+        set(value) {
+          redefine(aObject, name, value);
+        },
         configurable: true,
         enumerable: true
       });
     }
   },
 
   /**
    * Defines a getter property on the given object for each of the given