Figuring out why running Ghostery on FiveThirtyEight locks up a browser

I follow what's published on FiveThirtyEight via their RSS feed. On average, I probably click through to one article a week. A week or two ago, I clicked through to a FiveThirtyEight story and the browser tab where it was loading became unresponsive. My reaction was "somehow they managed to get around Ghostery and do some advertising/tracking shenanigans to freeze my browser, oh well", and I closed the tab. The tab hung around for a minute but then went away. I figured it was an isolated incident.

Fast-forward to this morning. I saw a story I was interested in reading in my feed reader so I clicked the link. New tab opens, and is immediately unusable. I can't scroll, I can't open dev tools, I can't close the tab. This time I was a bit more motivated to figure out what was going on.

Once I got the rogue tab closed, I whitelisted FiveThirtyEight. Then the page loaded and operated as expected. But by this point, I couldn't care less about the actual content—I wanted to know why the page froze when running an ad blocker.

If you've run an ad blocker for any amount of time, you have seen weird, half-rendered pages while cruising around the web. Occasionally sites will only render their content after some combination of advertising and tracking scripts have loaded. Others will show you a pop-up if they detect a blocker and ask to be whitelisted. Sometimes I whitelist, sometimes I give up and forget about whatever previously grabbed my attention.

This FiveThirtyEight experience was different. Missing content and broken pages, I understand that. A locked up browser tab from a reputable site I frequent is something different. I really wanted to figure out what was happening.

Google Chrome Task Manager showing Ghostery at greater than 100% utilization

Ghostery was clearly the culprit, but why? I've been using it for a couple years now, and can't recall having a problem like this in the past.

I started re-blocking each tracker identified by Ghostery. I'd block one, reload. If the page worked, I went to the next one. None of the trackers from the big, recognizable companies (Google, Facebook, Adobe) were causing the problem. I could block those and the page still worked.

I narrowed down the problem to the advertising tracker called "NetRatings SiteCensus". I'd never heard of it, but apparently it's something from Nielsen (of TV ratings fame, I think).

Visiting the page in question with dev tools gave me a little more info:

Google Chrome Developer Tools' Network section showing an inordinate number of requests to a tracking script

Over 2300 requests, running one request per millisecond based on the timestamp in the URL query string. That's enough to freeze any browser tab. The full URL to the script in question is: https://cdn-gl.imrworldwide.com/novms/js/2/ggcmb510.js?_=timestamp. That script wasn't necessarily problematic, but rather all the requests to get it.

Looking at the "Initiator" for all these requests, I headed over to https://secure.espn.com/combiner/c?js=jquery-1.10.2.js,plugins/jquery.pubsub.r5.js,analytics/visitorAPI_156.js,analytics/sOmni_161.js&ver=20180126. Digging through there, I found the place where some javascript was making a request to get the js file from imrworldwide.com:

FiveThirtyEight javascript inserting additional javascript from a tracking company

Now I was getting somewhere: espn.track.initNielsen was trying to load the script in question, then executing a function to call espn.track.nielsen. Who was calling initNielsen? Well, nielsen, of course. The infinite loop was taking shape.

What about the next level up the stack? Who starts this nielseninitNielsennielsen madness?

After diligently dissecting 8k lines of code... just kidding, I ⌘ + f-ed around until I figured out that espn.track.nielsen is first called by another function called init defined inside of another function called espn.track.trackPage. That one, trackPage, is called by another function called loadOmniture that's defined in a <script> tag in the page's HTML just inside the closing body tag. Whew.

Here's a recap starting with page load, rather than working backward from the two functions that endlessly call each other:

  1. Page loads
  2. <script> tags in the page are executed
  3. One <script> runs loadOmniture if !window.___skipShowAds is true
  4. loadOmniture calls espn.track.trackPage passing an object with a property called enableNielsen that is truthy
  5. trackPage defines a local function called init and runs it
  6. init calls espn.track.nielsen when the aforementioned enableNielsen property is true-ish
  7. our new friend nielsen checks if a global variable named NOLCMB exists
  8. when there's no variable named NOLCMB, initNielsen gets called
  9. initNielsen tries to insert the Nielsen NetRatings SiteCensus script and runs nielsen again

Ghostery blocks that script, so we get an endless loop where each loop iteration makes a request.

Why doesn't this happen without an ad blocker? That is because the Nielsen script in question, https://cdn-gl.imrworldwide.com/novms/js/2/ggcmb510 defines a global named NOLCMB when it loads, thereby avoiding the infinite loop caused by initNielsen (steps 7 and 8 above).

The NOLCMB global acts as a flag as to whether or not the Nielsen tracker is loaded. The code naively assumes that when the tracker isn't loaded, it can load it. And so it tries over and over.

In summary: request blocked? Do it again! Again! And again. Yep, again. Repeat thousands of times. browser tab dies

This is the one of the few experiences I've had where running an ad-blocker made a page less usable. And since I'd like to avoid that, I've left my whitelisting of FiveThirtyEight in place.