I recently saw a tweet by Dr. Augustine Fou about IntersectionObserver, a recently added browser feature that let’s scripts easily measure the visibility of an element within the webpage, relative to another element or to the browser viewport. check out this great example provided by Mozilla. Historically, this task used to be challenging since no standard, cross browser method to do so was available, let alone when the measurement script was running inside cross origin (“unfriendly”) iframe.
As a result, ads viewability was sold as a standalone feature by different measurement vendors, often combined with bot detection and brand safety capabilities, in order to verify the advertisers get what they pay for: ad that’s viewable by human in brand safe environment.
However, since no standard method of measuring viewability was available, each company used it’s own set of hacks which resulted non-negligible discrepancies between different vendors, just like the situation with bot detection today.
In this blog post, I will discuss the different methods used by ad viewability measurement scripts. One might think that the IntersectionObserver API deprecates the older methods, but that’s not exactly the case. In the context of bot detection, there’s an importance to measure viewability with as many methods as possible, since some bots will try to spoof the viewability measurements but won’t necessarily spoof all methods. A discrepancy between different viewablilty measurement methods can serve as a strong signal of ad wrongdoing.
Historically, the different measurement methods could be categorized into four categories:
- Browser optimizations
- Browser bugs
- Browser APIs
As the name suggests, this method uses geometric calculations in order to determine the viewability of the ad. Once you have viewport size (which used to be a challenging thing of in itself) and the ad BoundingClientRect, you can pretty easily and accurately determine how much, if at all, of the ad is in view. The problem of this approach is that it don’t work if the measurement script is running inside cross origin iframe, while a lot of ads are served into them.
Pioneered by Spider.io (acquired by google), this approach takes advantage of several optimizations the browser makes in order to save computational resources. For example, there’s no reason to render background tabs, so the browser will throttle timers (see these crbugs) and animations created with requestAnimationFrame. It’s possible to measure the time difference between consecutive callbacks initiated through this function and infer the rendering FPS from it. Same optimization exists in ActionScript (remember when Flash was still a thing?) and could be measured similarly with the frameEnter event. The main benefit of this method that it works well inside cross origin iframes, so they significantly increased the coverage of ad viewability measurement.
DoubleVerify famously exploited a vulnerability in Internet Explorer version 6-10 that allowed scripts to read values from properties such as clientX, clientY, offsetX, offsetY, screenX, screenY, x and y from mouse events dispatched by the fireEvent method. These values allows them to calculate the viewport size inside cross origin iframes, which was a breakthrough back in the day.
Another Internet Explorer bug I’ve seen used for viewability measurement, is within the htmlfile ActiveX object. It created a popup object using the createPopup method, and then call the show method on the popup object. When the tab is in the background, the call will throw an exception.
Browser standards introduced several events and APIs that let scripts measure viewability related data point, such as:
- IntersectionObserver, as mentioned above
- document.hasFocus (expected to be true if the ad was clicked)
- Page visibility API introduced document.visibiltyState (visible, hidden, prerender, unloaded) and document.hidden along with the visibilitychange event.
- Firefox specific window.mozPaintCount tells how many paints occurred for current document. Chrome showed interest in implementing something similar but that didn’t happen eventually.
- document.elementFromPoint returns null if the specified point is outside of the visible area
There’s a distinction to made between two different levels of these methods: tab level and frame level. Tab level methods like the page visibilty API let you know about the visibilty state of the tab, while others like mozPaintCount let you know about the visibility state of a specific frame.
Another corresponding distinction is positive vs negative measurement capability. Some methods, like the geometric and intersection observer, allow the script to tell whether (and how much) of the ad is viewable or not – so they have positive and negative measurement capabilities. Other methods like document.hidden property, since it works on the tab level, can only tell you if the ad is not viewable – negative measurement capability.
When specific browser optimization is applied on the frame level, like mozPaintCount and requestAnimationFrame throttling, viewability vendors use a nice trick in order to produce measurement that’s compliant with MRC definition of viewable ad: “at least 50% of the ad being viewed for 1 continuous second or more”.
The trick is to create 5 separate iframes, each ones contains a frame level measurement code, and placing them in the top and bottom right and left, and one in the center of the ad. If 3 or more iframes are viewable for 1 second, the ad deemed as viewable:
Pretty cool, huh?
Remember, it’s possible to spoof all of the methods above by either making the browser APIs behave as if the ad is viewable, or by just replying the “viewable” signal of the measurement script, as discussed with bot detection replay attack in the previous post.