DEV Community

Oryan Moshe
Oryan Moshe

Posted on • Originally published at Medium

The object that misbehaved — window.performance.memory

How I troubleshoot issues that don’t make sense

Today we wanted to start tracking some new performance metrics on our landing pages, and more specifically, the memory usage.
As usual, we got the data from our trusty window.performance object (from now on will be referred to as "the enemy"), stringified it before sending and…

Surprisingly, all we got was "{}"!

How can it be? We have an object, with properties we can access, but stringifying it returns an empty object!


Down the rabbit hole I go

I started digging deeper into it, with the goal of turning the enemy into a string without hard coding the property names.

I tried spreading it
  const result = { ...window.performance.memory };
  console.log(result); // {}
Enter fullscreen mode Exit fullscreen mode
I tried getting its components
  const keys = Object.keys(window.performance.memory);
  const values = Object.values(window.performance.memory);
  console.log(keys); // []
  console.log(values); // []
Enter fullscreen mode Exit fullscreen mode
I tried destructuring it
  const { jsHeapSizeLimit } = window.performance.memory;
  console.log(jsHeapSizeLimit); // 2330000000
Enter fullscreen mode Exit fullscreen mode

And surprisingly — it worked!

With great confidence that I’m going to crack this I tried "rest" destructuring:

  const { ...rest } = window.performance.memory;
  console.log(rest); // {}
Enter fullscreen mode Exit fullscreen mode

What is going on? Why does this specific object refuse to play nice?


A light at the end of the tunnel (that turns out to be a train)

After some more tinkering I found another thing that didn’t make sense.

  for (const prop in window.performance.memory)
    console.log(`${prop}: ${window.performance.memory[prop]}`)
  // totalJSHeapSize: 13400000
  // usedJSHeapSize: 12700000
  // jsHeapSizeLimit: 2330000000
Enter fullscreen mode Exit fullscreen mode

Iterating over the enemy’s properties does work, but getting its properties names doesn’t? Even Object.getOwnPropertyNames failed!

Although this solution could work for me (remember, my original goal was "turning the enemy into a string without hard coding the property names") I wanted to find a more elegant solution, and wanted to get to the bottom of this.


The first solution, AKA "not good enough"

The next part of my trip was when I started playing around with the prototype of the enemy, called MemoryInfo. I tried changing the prototype, assigning the enemy to a new object with a different prototype, creating an array from the enemy, and eventually playing around with a combination of all the techniques mentioned above.

  Object.getOwnPropertyNames(window.performance.memory); // []
  Object.getOwnPropertyNames(window.performance.memory.__proto__) // 
  ["totalJSHeapSize", "usedJSHeapSize", "jsHeapSizeLimit", "constructor"]
Enter fullscreen mode Exit fullscreen mode

Success! Well… Sort of. I got the prototype property names, and I could use this array to get what I want using the following snippet

  const success = JSON.stringify(Object.getOwnPropertyNames(window.performance.memory.__proto__).reduce((acc,key) => {
       if (key !== 'constructor')
         acc[key] = window.performance.memory[key];
       return acc;
    }, {})
  );

  console.log(success) // " {"totalJSHeapSize":20500000,"usedJSHeapSize":18200000,"jsHeapSizeLimit":2330000000}"
Enter fullscreen mode Exit fullscreen mode
Not great, not terrible.

And it does work (and it is a one-liner like I love), don’t get me wrong, but it’s just not as elegant as I wanted it to be, and nothing made any sense anymore.


I am one with the code

Fast forward 1 hour and I discovered that objects have properties, and these properties have metadata (called descriptor) and this metadata defines whether we can enumerate over the property, change it, and how we get it from the object.

So it must be that the properties have some sort of a metadata tag that prevents getting them in the conventional ways. From MDN I figured that enumerable is the property that interests me:

true if and only if this property shows up during enumeration of the properties on the corresponding object.

  Object.getOwnPropertyDescriptors(window.performance.memory.__proto__);
// {..., jsHeapSizeLimit: {get: ƒ, set: undefined, enumerable: true, configurable: true}, ...}
Enter fullscreen mode Exit fullscreen mode

But the properties do have the enumerable metadata-property turned on, why aren’t they showing up when using Object.keys or Object.values?


The not-so-grand finale

Finally, I succeeded in "turning on" the enumerable flags on the enemy, and my data stringified beautifly

  const finale = JSON.stringify(Object.defineProperties(window.performance.memory, Object.getOwnPropertyDescriptors(window.performance.memory.__proto__)));
  console.log(finale); // "{"totalJSHeapSize":29400000,"usedJSHeapSize":23100000,"jsHeapSizeLimit":2330000000}"
Enter fullscreen mode Exit fullscreen mode
Sweet, sweet one-liner

So what’s going on here?

We already saw that the descriptors of the MemoryInfo prototype should be fine, so the only explanation is that somehow they are not set (or overridden) on the enemy itself.

Using Object.defineProperties I am able to get a copy of the enemy, but with the correct descriptors (which we can just get from the prototype using Object.getOwnPropertyDescriptors)

Then just stringify and ship!

Top comments (0)