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); // {}
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); // []
I tried destructuring it
  const { jsHeapSizeLimit } = window.performance.memory;
  console.log(jsHeapSizeLimit); // 2330000000

And surprisingly — it worked!

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

  const { } = window.performance.memory;
  console.log(rest); // {}

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

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"]

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}"
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.

// {..., jsHeapSizeLimit: {get: ƒ, set: undefined, enumerable: true, configurable: true}, ...}

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}"
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!

Discussion (0)