Meteor.js bug hunting

Speeding from roadblock to roadblock

Oliver Song
Meteor Hammer

--

If you know me, you probably know that I’m a huge fan of Meteor. I love the framework, the new paradigm it brings to web development, and the company itself. You get a lot of great things by using Meteor, but you need to follow its rules. Sometimes, those rules break things in mysterious ways.

The bug

At the time, I was working on fortnight.io, a load balancing to-do list. When you go to the landing page, a large video loops as the background. If you went to another page then back to the landing page, the video would freeze. No errors in the console.

I had a couple key dependencies:

Deep dive

At first I thought it was a dependency load order bug, but that checked out. Then I thought I could just replace BigVideo.js with a <video> tag, but BigVideo.js actually provides significant benefits on top of the regular HTML tag.

I did a deep dive with the BigVideo source code, stepped through it with the debugger, and couldn’t find a single thing. It seemed like all the libraries were loading correctly. The same exact code was running on both page loads.

At this point, I emailed Tom Coleman, one of the core developers on a lot of cool Meteor projects, including IronRouter. He suggested the DOM might not be rendered when the second redirect happens, so the video element doesn’t actually get instantiated. I tried using a long delay, and but it still didn’t work.

Another deep dive

Tom’s suggestion made me examine the exact HTML that was output by BigVideo. It turns out that when BigVideo runs, it inserts something into the DOM, then calls Video.js. When Video.js runs, it modifies that DOM element. So now I could tell what was functioning and what wasn’t, based on the final DOM output. When the video was freezing, the DOM output reflected that Video.js hadn’t run. How could this be?

I did another deep dive, this time into Video.js. After a long walk through the code, I found this gem:

// If a player instance has already been created for this ID return it.
if (vjs.players[id]) {
return vjs.players[id];
// Otherwise get element for ID
} else {
tag = vjs.el(id);
}

Videojs doesn’t let the same video element be instantiated twice.

I tried setting resetting the players object before running the BigVideo code, but it returned a strange error.

vjs.players={};
> error: vjs.players is undefined

As it turns out, the Video.js code is all minified. Instead of pasting every source file (there are a lot of them) into my project, I walked through the minified code to find the attribute on vjs that held something that looked like my video. Thanks to some Chrome inspector magic, I found it- Adding a single line before the BigVideo code ran fixed the bug.

vjs.La={};

Reflection

This is a great example of how Meteor’s complexity and rules create some really tough bugs. The key insight is this: when using IronRouter and Meteor, the Window object persists. Because of this, the vjs object kept track of a player that had already been removed from the DOM. If you have a library like Video.js that sets an attribute on Window, there can be unforeseeable consequences later on.

Maybe Meteor or IronRouter should reset the Window object somehow between page transitions? Would that be a feature or a bug? I’m not sure.

Either way, this little bug took me for a wild ride. Hopefully you don’t run into the same thing. Thanks for reading!

--

--

Oliver Song
Meteor Hammer

Cyclist, gamer, engineer. Currently @SamsaraHQ. Previously @claralabs, @square, @tumblr, @MIT.