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.
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:
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:
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 nielsen
→ initNielsen
→ nielsen
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:
- Page loads
<script>
tags in the page are executed- One
<script>
runsloadOmniture
if!window.___skipShowAds
is true loadOmniture
callsespn.track.trackPage
passing an object with a property calledenableNielsen
that is truthytrackPage
defines a local function calledinit
and runs itinit
callsespn.track.nielsen
when the aforementionedenableNielsen
property is true-ish- our new friend
nielsen
checks if a global variable namedNOLCMB
exists - when there's no variable named
NOLCMB
,initNielsen
gets called initNielsen
tries to insert the Nielsen NetRatings SiteCensus script and runsnielsen
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.