Mind Chasers Inc.
Mind Chasers Inc.

Review of Javascript undefined & null in the Context of Detecting Blocked Ads

Brief review of the use of the Javascript undefined and null primitives using the Debugger Console and ad blocking as an example

Overview

Many of us use undefined and null on a daily basis in our Javascript but are confused as to how these "things" are represented in a Javascript engine, console, etc. This brief article reviews the use of undefined and null in the context of detecting whether a third-party Adsense ad is being both served and not blocked in a browser window.

Yes, we're guilty of inserting some third-party ads in our articles, and we know that some of our site visitors are using ad blockers. Ad blockers are great, but they can often leave blank space or empty borders on a page. Therefore, let's try to detect when an ad is blocked and replace the blank space with something.

Of course, the other big problem with blocking ads and third party tracking sites is that it often breaks the page, sometimes to the point that it's no longer usable. This is the fault of the web developer and not the user. If you're using a content filter and it breaks a site, we encourage you to give the company hell over it. We have a right to block undesirable content in our browser window.

The figure below shows an excerpt of one of our pages showing an Adsense ad with the Firefox debugger console open. You can see that we can retrieve the ad's iframe object with a little Javascript.

fire fox adsense
Figure 1. FireFox split screen with Adsense ad and debugger console

With ads visible / not blocked1:

>> $(".adsbygoogle iframe")
<iframe id="aswift_0" ... >

With ads blocked:

>> $(".adsbygoogle iframe")
null

Shown below is the structure of our HTML that serves the ad:

<script>mcAds="Adsense"</script>
<div id="mc-ad-banner">
	<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
	<ins class="adsbygoogle"
		...
	</div>	
</div>

The mcAds variable is injected into the Javascript's global (window) object by our server if ads are being served. Otherwise, the script tag will not be present, and mcAds will not be defined.

Therefore, we have created two tests to determine whether ads are being served and not blocked:

  1. Whether mcAds is defined
  2. Whether our DOM query of ".adsbygoogle iframe" returns null.

In this article, we take the approach of embedding a commented, non-minimized script within the HTML of this page "static/src/js_tst_ads.js", and we encourage the reader to step through the code line by line in the developer console of their browser in a split screen fashion. The entire script is also included at the bottom (as HTML) and some snippets of the code are shown throughout the page.

If you plan to debug along while reading this article, go ahead and open your developer console. On Windows & Linux: CTRL+SHIFT+S should work, and on MacOS: COMMAND+OPTION+I should do it. Note that we provide references at the bottom of this article for both Firefox's Debugger and Safari's Web Inspector. These are the two that we use extensively and like very much.

Javascript Primitives

Both "undefined" and "null" are primitive values. However, when we see these terms, it can be confusing as to whether we are referring to the value or the property / literal that holds one of these two values.

From the ECMAScript specification: "A primitive value is a member of one of the following built-in types: Undefined, Null, Boolean, Number, and String."2

And this can also be confusing because there are Boolean, Number, and String constructors, but constructors don't exist for Undefined and Null.

Again from the ECMAScript Specification:

  • Undefined type has exactly one value, called undefined. Any variable that has not been assigned a value has the value undefined.
  • An undefined value is a primitive value used when a variable has not been assigned a value
  • Null type has exactly one value, called null.
  • A null value is a primitive value that represents the intentional absence of any object value

An odd thing with null is that performing a typeof operation on null returns "object". We are asked to understand that this is for historical reasons. Fortunately, stepping through code in Safari's Web Inspector will properly show the Null type:

console: null type
Figure 2. Debugger console showing types including Undefined and Null

And we find it somewhat confusing that our Safari Web Inspector console shows both s1 and s2 as a String type when s1 holds a primitive and s2 holds an object. Fortunately, the difference is conveyed clearly in the local variable scope viewer to the right.

Another odd thing about these two primitives is that undefined is a property of the global (window) object but null is not:

console.log(undefined in window);	// true
console.log(null in window);		// false

So, it seems that undefined is a property of the global object that holds the primitive value undefined, which is of the single value Undefined type.

And we have shown above that null is not a property of the global object. Instead, Mozilla tells us that null is in fact a literal. Regardless, null holds the null primitive value, which is of the single value Null type.

Detecing a blocked ad

We're not aware of documentation by ad providers on how to detect the presence of their ad or when it's blocked. Therefore, we need to do some testing on our own page. For Adsense, an ad basically consists of a script tag and an ins tag with class "adsbygoogle" (as shown previously). If Adsense isn't blocked, it loads it's script, and this script loads images and many other scripts (yuck!), as shown below in our Firefox Debugger Console, Network tab:

fire fox adsense network loading
Figure 3. Firefox Network Tab showing Adsense loading

We have found that the ins Element will have an iframe child Element inserted in the DOM, and we can run a query for this iframe to determine whether the ad was blocked. Of course, we can get a lot more sophisticated, but this approach suffices for the purposes of this article.

We know that Element.query​Selector() will return null when the selector isn't found. In this case, we're looking for ".adsbygoogle iframe", and we know from above that null is used to represent the "intentional absence of any object value".

As for undefined, we use this in our Javascript to detect cases when we don't serve up an ad because a user has been granted special privileges on our site. For these cases, we don't include the Adsense code in our DOM, and we can detect this as follows:

if (mcAds === "undefined") {
	...
}

If you are viewing this article as a visitor, which is probably the case, then you should see that mcAds is defined (a string).

The other thing that can be tricky about detecting blocked ads is that the blocking of Javascript (ads) can prevent other resources from loading, which can cause scripts to hang. Notice that we use the following event handler to load our test script:

window.addEventListener("DOMContentLoaded",function(e) { 
	...
}

This uses the DOMContentLoaded event. We find that if we use the load event, then our scripts may fail to execute while waiting on a third party script(s) to load.

Finally, let's detect whether the ad has been blocked. If it has, let's replace it with a desirable image. If you can sucessfully block the ad, you should see a picture in its place of some cute puppies. Of course, you can also modify the script to see the puppies (hint: delete mcAds inside a breakpoint).

Note that you'll see in our script that we run the ad block detection logic inside a function that acts as a setTimeout handler. We do this to give the Adsense script time to load and insert the iframe into the DOM.

Script

Javascript, js_tst_ads.js
/* 
 * Copyright 2019 Mind Chasers Inc.
 * file: js_tst_ads.js
 * Example code only; don't use for production
 */

window.addEventListener("DOMContentLoaded",function(e) { 

	// show that Undefined and Null are not constructors
	try {
		console.log(String());		// 
		console.log(Boolean());		// false
		console.log(Number());		// 0
		console.log(Undefined());	// throws error
		console.log(Null());
	}
	catch(e) {
		console.log(e);				// ReferenceError: Can't find variable: Undefined
	}
	
	// Inspect the types of variables we create
	let und1;		// undefined since we didn't define it
	let null1 = null;
	let s1= "abc";
	let s2 = new String("abc");
	
	console.log(typeof und1 === "undefined")	// true
	console.log(typeof null1 === "null")		// false
	console.log(typeof null1 === "object")		// true
	console.log(typeof s1 === "string")			// true
	console.log(typeof s2 === "object")			// true
	
	// inspect whether undefined or null are properties of window
	console.log(undefined in window);			// true
	console.log(null in window);				// false
	
	// detect whether ads are blocked
	setTimeout(detectBlockedAds, 1000);			// give the ad a chance to load
	
});

// If our ad hasn't loaded, then let's display some puppies instead
function detectBlockedAds(event) {
	if ( typeof mcAds !== "undefined" && $('.adsbygoogle iframe') === null) {
		let img = new Image();
		img.src = "https://upload.wikimedia.org/wikipedia/commons/1/17/Westie_pups.jpg";
		img.onload = function(event) {
			$('#mc-ad-banner').innerHTML = '<img height=200 src="' + this.src + '">';
		};
	}
}

Footnotes

  1. $() function is defined locally as:
    function $(selector, el) {
    	if (!el) {
    		el = document;
    	}
    	return el.querySelector(selector);
    }
    
  2. We purposely left out the Symbol type

References

Didn't find an answer to your question? Post your issue below or in our new FORUM, and we'll try our best to help you find a solution.

And please note that we update our site daily with new content related to our open source approach to network security and system design. If you would like to be notified about these changes, then please follow us on Twitter and join our mailing list.

Related articles on this site:

share
subscribe to mailing list:

Please help us improve this article by adding your comment or question:

your email address will be kept private
authenticate with a 3rd party for enhanced features, such as image upload
previous month
next month
Su
Mo
Tu
Wd
Th
Fr
Sa
loading