Schalk Neethling

Scripting on Caffiene

Respect User Choice ~ Do Not Track

| Comments

Two of the cornerstones of the open web is choice, and trust. Recently more and more attention has been focused on a practice that's eroding these cornerstones. I am here of course referring to online tracking as done by, among others, behavioral advertising companies, governments, mobile carriers, ISPs online. Not only are they breaking user trust by tracking them without permission, they do not offer users a choice in this regard.

In fact, it is even worse than that.

Thanks to work initiated by Mozilla, users have a way off telling the above mentioned players that they wish to not be tracked online. This mechanism is known as Do Not Track. Going over the mechanism of how Do Not Track works and is implemented, is outside the scope of this post. Read the EFF article above if you wish to learn the details.

What this post is about, is giving you a simple JavaScript utility that will enable you to respect your user' choice, by honoring their Do Not Track status.

"But I do not have any advertising on my site.", you might say. Well, if you use tools such as Google Analytics or Google Tag Manager to users access statistics or, to track user behavior, your users are being tracked. If you are using Optimizely, to do A/B testing on your site, you users are being tracked.

If you embed social buttons, or use something like AddThis for social sharing, your users are being tracked. Even after they have left your site.

You may also sight a recent article on ars technica regarding a FCC ruling that websites can continue to ignore Do Not Tack. You could even argue that users have a multitude of options to protect themselves if they wish to not be tracked, such as uBlock, Privacy Badger, Ghostery and more recently, using Firefox' Private Browsing with Tracking Protection. But the onus should not be on the user.

As with accessibility and a good user experience, honoring the choices made by your users is the right thing to do, with the positive side effect of increasing user trust. With all of that said, let’s move on to the how of this post.

The core of this is a JavaScript function that returns the current status of the Do Not Track flag. You can thus use this to wrap for example, your GA scripts.

1
2
3
4
5
6
7
8
9
10
// include the helper
<script src="js/libs/dnt-helper.js"></script>
// If doNotTrack is not enabled, it is ok to add GTM
if (!_dntEnabled()) {
  (function(w,d,s,l,i,j,f,dl,k,q){
    w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});f=d.getElementsByTagName(s)[0];
    k=i.length;q='//www.googletagmanager.com/gtm.js?id=@&l='+(l||'dataLayer');
    while(k--){j=d.createElement(s);j.async=!0;j.src=q.replace('@',i[k]);f.parentNode.insertBefore(j,f);}
  }(window,document,'script','dataLayer',['YOUR-GTM-ID}}']));
}

That’s it. You will now only be adding Google Tag Manager/Google Analytics, if the current user has not enabled Do Not Track.

The Technical Details

Let’s step though some of the details regarding the implementation of the helper. The first part is actually getting the status of the doNotTrack property. Now, it should be a simple process but, due to browser differences, it turns out to a bit more effort than it needs to be.

1
var dntStatus = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;

According to the Tracking Preference Expression(DNT) specification, the property should be on the navigator object and, it is thus in Firefox, Chrome and newer versions of Opera(using blink).

For some time, whilst the specification was not yet a recommended standard and, Microsoft’s implementation differed from the draft, it existed as a prefixed navigator.msDoNotTrack property in Internet Explorer. That was until, in Internet Explorer 11, Microsoft decided to embrace the specification and not enable Do Not Track by default but, what they also did, was to move the property from navigator to window. And here we are, having to jump through hoop after hoop to simply get the value of this property.

Note: The property is also set on the window object in Safari.

Next we need to talk about the following line:

1
var anomalousWinVersions = ['Windows NT 6.1', 'Windows NT 6.2', 'Windows NT 6.3'];

As mentioned above, there was a lengthy period where Microsoft’s implementation did not follow the standard and enabled Do Not track by default. This meant, it was no longer an indication of user choice. Thus, it makes it impossible to honor the setting and this is why we need the Array of affected Windows versions above.

Skipping over the regular expressions bits we get to a pretty large conditional block. Here we then need to figure out the real status of Do Not Track before returning the result to the caller. First up, we handle old versions (lte IE8) where this flag did not exist.

1
2
3
4
// With old versions of IE, DNT did not exist so we simply return false;
    if (isIE && typeof Array.prototype.indexOf !== 'function') {
        return false;
    ...

No point in doing anything more, we can simply return false and be done with it. Now, Firefox has not been entirely innocent here either:

1
2
3
} else if (fxMatch && parseInt(fxMatch[1], 10) < 32) {
    // Can't say for sure if it is 1 or 0, due to Fx bug 887703
    dntStatus = 'Unspecified';

For versions of Firefox prior to version 32, there was a bug with the return value of the doNotTrack property. The tl;dr is that the "yes" value meant that the user has expressed an intent, but not what the intent was. Therefore we need to set the return value as Unspecified. Next up is handling the anomalous Windows versions where Do Not Track was set to true by default.

1
2
3
4
} else if (isIE && platform && anomalousWinVersions.indexOf(platform.toString()) !== -1) {
    // default is on, which does not honor the specification
    dntStatus = 'Unspecified';
    ...

As mentioned earlier, in these cases we cannot trust the setting of the Do Not Track flag to be user specified and thus need to default to "Unspecified" i.e. false.

1
2
3
4
5
} else {
    // sets dntStatus to Disabled or Enabled based on the value returned by the browser.
    // If dntStatus is undefined, it will be set to Unspecified
    dntStatus = { '0': 'Disabled', '1': 'Enabled' }[dntStatus] || 'Unspecified';
}

With all of that out of the way, we can handle the last happy case where we simply use the value returned by the browser but, handling possible outlier cases where the value is undefined. The only thing left is to return true or false if we have not already returned in an earlier step.

I sincerely hope people find this little utility useful, and will use it on their sites. When we respect our user' privacy and choices, users, and the open web, wins and that, is always a good thing.

I look forward to your comments.

Comments