javascripthtmlreactjsadblock

Detect adblock with react


I am trying to detect if AdBlock plus is running in firefox, it does not need to work 100% of the time, but I at least want to disable the built in adblockers that come with firefox and chrome.


It looks like the most promising solution is from this blockAdBlock package. They show an example which works, but it requires manipulation of the main index.html file. I'm using Gatsby, which doesn't really give you access to the .html files, so I would like to detect the adblocker in my component.

The code in their blockadblock.js file is an IIFE, which I don't understand too well, but I understand enough to know that it's invoked when it's created.

If I just copy paste the code from that file and place it inside my component and then try to check for the adBlocker, it looks like blockAdBlock is never undefined

const BAB = (function(window) {...})(window);

if (typeof blockAdBlock === 'undefined'){     //Always true
    alert('works')
    canRunAds = false
 }

If their example works, I feel that I should be able to get something working from it.


Most Solutions I've seen

Every common answer I've seen which uses something like putting a div in which looks like

<div id="ad-container">
  <img src="../ad/ad.png" id="ad">
</div>

and then uses some javascript to check if 'ad-container' has a height > 1. My divs which names like "ad-container" (or ad, ad-block, ad-banner) are not removed by an adblocker so this method is faulty.


Solution

  • FuckAdBlock/BlockAdBlock library works by simulating patterns (specifically filtered CSS classes) that AdBlockers are known to block and checking if it gets blocked. (You could do something similar by analyzing adblocker patterns and embedding them in your page).

    Using FuckAdBlock project example. Loads script dynamically at runtime from cdnjs by injecting a script tag:

    // Function called if AdBlock is not detected
    function adBlockNotDetected() {
        alert('AdBlock is not enabled');
    }
    // Function called if AdBlock is detected
    function adBlockDetected() {
        alert('AdBlock is enabled');
    }
    
    // We look at whether FuckAdBlock already exists.
    if(typeof fuckAdBlock !== 'undefined' || typeof FuckAdBlock !== 'undefined') {
        // If this is the case, it means that something tries to usurp are identity
        // So, considering that it is a detection
        adBlockDetected();
    } else {
        // Otherwise, you import the script FuckAdBlock
        var importFAB = document.createElement('script');
        importFAB.onload = function() {
            // If all goes well, we configure FuckAdBlock
            fuckAdBlock.onDetected(adBlockDetected)
            fuckAdBlock.onNotDetected(adBlockNotDetected);
        };
        importFAB.onerror = function() {
            // If the script does not load (blocked, integrity error, ...)
            // Then a detection is triggered
            adBlockDetected(); 
        };
        importFAB.integrity = 'sha256-xjwKUY/NgkPjZZBOtOxRYtK20GaqTwUCf7WYCJ1z69w=';
        importFAB.crossOrigin = 'anonymous';
        importFAB.src = 'https://cdnjs.cloudflare.com/ajax/libs/fuckadblock/3.2.1/fuckadblock.min.js';
        document.head.appendChild(importFAB);
    }

    Using BlockAdblock project example. Includes blockadblock.js from cdnjs CDN:

    // Function called if AdBlock is not detected
    function adBlockNotDetected() {
        alert('AdBlock is not enabled');
    }
    // Function called if AdBlock is detected
    function adBlockDetected() {
        alert('AdBlock is enabled');
    }
    
    // Recommended audit because AdBlock lock the file 'blockadblock.js' 
    // If the file is not called, the variable does not exist 'blockAdBlock'
    // This means that AdBlock is present
    if(typeof blockAdBlock === 'undefined') {
        adBlockDetected();
    } else {
        blockAdBlock.onDetected(adBlockDetected);
        blockAdBlock.onNotDetected(adBlockNotDetected);
        // and|or
        blockAdBlock.on(true, adBlockDetected);
        blockAdBlock.on(false, adBlockNotDetected);
        // and|or
        blockAdBlock.on(true, adBlockDetected).onNotDetected(adBlockNotDetected);
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blockadblock/3.2.1/blockadblock.min.js" integrity="sha512-EFY34xQ/AKRSb4EfjeRCO1TXnLuDQrYlo3BVId+DU8J4BiKUezCWK93bUlXTkEf4a8rMRroouaPXHnq/WTK4pA==" crossorigin="anonymous"></script>


    Just checking the height of the iframe works for me:
    (Using ResizeObserver to hook when the size of the iframe changes. setTimeout after 5s for lack of ResizeObserver support.)

    Note: Check support table in above link and/or use a polyfill for ResizeObserver.
    eg: iOS Safari only 13.4+ which may not be an acceptable level of support for many people

    fn = () => document.querySelector('.jellyWidget').clientHeight > 0 || alert('blocked')
    
    fn()
    setTimeout(fn,5000)
    typeof ResizeObserver!=='undefined' &&
      new ResizeObserver(fn).observe(document.querySelector('.jellyWidget'))
    

    fn = () => document.querySelector('.jellyWidget').clientHeight > 0 || alert('blocked')
    
    fn()
    setTimeout(fn,5000)
    typeof ResizeObserver!=='undefined' &&
      new ResizeObserver(fn).observe(document.querySelector('.jellyWidget'))
    <iframe src="//rcm-na.amazon-adsystem.com/e/cm?o=15&amp;p=14&amp;l=ur1&amp;category=biss&amp;banner=05GNDH2E5A6MQH5KAZ02&amp;f=ifr&amp;linkID=3980418b7a5cc00e6f0e0fac51cf69f9&amp;t=suddenlysas06-20&amp;tracking_id=suddenlysas06-20" scrolling="no" style="border: medium none;" class="jellyWidget undefined" width="160" height="600">#document<head><script type="text/javascript">
        /**
     * Created by pedapav on 4/1/15.
     *
     * Tracking utilities to be used by client side rendering templates.
     */
    
    window["trackingUtils"] = function(regionInt, foresterChannelUrlPrefix, impressionRecorderPrefix, pixelUrl,
                                       clickUrl, impressionToken, slotNum, subtag, ABPPixelURL, disableABPCheck, AESPixelUrl) {
        var that = {},
            refMatch = new RegExp("\/(ref=[\\w]+)\/\?", "i"),
            TRANSIT_ID_KEY = "assocPayloadId",
            encodeStr = function (b) {
                return b && encodeURIComponent(b).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
            },
            rawRefURL = (function() {
                var alink = document.createElement("a");
                alink.href = (window.location !== window.parent.location) ? document.referrer : document.location;
                // on IE, path name does not begin with a '/' - append it.
                // on IE, 'host' property includes the port even if it is standard port 80/443
                // but on IE, document.location.href does not include standard ports
                // so we have to remove them before forming 'ref-refUrl'
                return alink.protocol + "//" + alink.hostname +
                    ((alink.port === "" || alink.port === "80" || alink.port === "443") ? "" : (":" + alink.port)) +
                    (alink.pathname.indexOf("/") !== 0 ? ("/" + alink.pathname) : alink.pathname);
            }()),
            refRefURL = (function() {
                return encodeStr(rawRefURL);
            }()),
            addQueryParameter = function(linkElem, paramName, paramValue, overwrite) {
                if(typeof paramValue === "string" && paramValue !== "") {
                    if (linkElem.search === "") {
                        linkElem.search = "?" + paramName + "=" + paramValue;
                    } else if(!linkElem.search.match(new RegExp("[?&]" + paramName + "="))) {
                        linkElem.search = linkElem.search.replace(/\?/, "?" + paramName + "=" + paramValue + "&");
                    } else if(overwrite) {
                        // query parameter already present - overwrite it, if forced
                        linkElem.search = linkElem.search.replace(new RegExp(paramName + "=([^&]*)"), paramName + "=" + paramValue);
                    }
                }
                return linkElem;
            },
            addQueryParameterStr = function(linkTarget, paramName, paramValue) {
                if(typeof paramValue === "string" && paramValue !== "") {
                    if (!linkTarget.match(/\?/)) {
                        linkTarget = linkTarget + "?" + paramName + "=" + paramValue;
                    } else if(!linkTarget.match(paramName)) {
                        linkTarget = linkTarget.replace(/\?/, "?" + paramName + "=" + paramValue + "&");
                    }
                }
                return linkTarget;
            },
            getEffectiveSlotNum = function(localSlotNum) {
                var finalSlotNum = (typeof slotNum !== "undefined") ? slotNum : 0;
                if(typeof localSlotNum !== "undefined") finalSlotNum = localSlotNum;
                return finalSlotNum;
            };
    
        that.addRefUrls = function (allLinks, linkId, linkCode, trackingId) {
            var amazonLinkPattern = new RegExp("^http://.*(amazon|endless|myhabit|amazonwireless|javari|smallparts)\\.(com|ca|co\\.jp|de|fr|co\\.uk|cn|it|es)/.*", "i"),
                href, results, i;
            for (i = 0; i < allLinks.length; i++) {
                allLinks[i].rel = "nofollow";
                href = String(allLinks[i].href);
                if (results = href.match(amazonLinkPattern)) {
                    allLinks[i].href = that.addTrackingParameters(allLinks[i], linkId, linkCode, trackingId);
                }
            }
        };
    
        that.addRefRefUrl = function(linkElem) {
            return addQueryParameter(linkElem, "ref-refURL", refRefURL);
        };
    
        that.getRefRefUrl = function() {
            return refRefURL;
        };
    
        that.getRawRefUrl = function() {
            return rawRefURL;
        };
    
        that.addSignature = function(linkElem, signature, signatureTimeStamp) {
            return addQueryParameter(
                    addQueryParameter(linkElem, "sig", signature),
                "sigts", signatureTimeStamp);
        };
    
        that.addLinkCode = function(linkElem, linkCode) {
            return addQueryParameter(linkElem, "linkCode", linkCode);
        };
    
        that.addTrackingId = function(linkElem, trackingId) {
            return addQueryParameter(linkElem, "tag", trackingId);
        };
    
        that.addLinkId = function(linkElem, linkId) {
            return addQueryParameter(linkElem, "linkId", linkId);
        };
    
        that.addSubtag = function(linkElem, subtag) {
            return addQueryParameter(linkElem, "ascsubtag", subtag);
        };
    
        that.addCreativeAsin = function(linkElem, adId){
            return addQueryParameter(addQueryParameter(linkElem, "creativeASIN", adId), "adId", adId);
        };
    
        that.addAdType = function(linkElem, adType) {
            return addQueryParameter(linkElem, 'adType', adType);
        };
    
        that.addAdMode = function(linkElem, adMode) {
            return addQueryParameter(linkElem, 'adMode', adMode);
        };
    
        that.addAdFormat = function(linkElem, adFormat) {
            return addQueryParameter(linkElem, 'adFormat', adFormat);
        };
    
        that.addImpressionTimestamp = function(linkElem, impressionTimestamp) {
        if (typeof impressionTimestamp === "number") impressionTimestamp = impressionTimestamp.toString();
            return addQueryParameter(linkElem, 'impressionTimestamp', impressionTimestamp);
        };
    
        that.convertToRedirectedUrl = function(linkElem, prefix, destParamName) {
            var alink = document.createElement("a");
            alink.setAttribute("href", prefix);
            if(typeof destParamName !== "undefined") {
                addQueryParameter(alink, destParamName, encodeStr(linkElem.getAttribute("href")), true);
            } else {
                alink.setAttribute("href", prefix + "/" + alink.getAttribute("href"));
            }
            linkElem.setAttribute("href", alink.getAttribute("href"));
            return linkElem;
        };
    
        that.getImpressionToken = function() {
            return impressionToken;
        };
    
        //we are using impressionToken as transitId right now,
        //can be changed to GUID in future
        that.generateTransitId = function() {
            return that.getHashedImpressionToken();
        };
    
        that.getHashedImpressionToken = function(){
            var pixelUrlParts = pixelUrl.split("/");
            //pixelUrl is http://pixelurl.com/x/px/HASHEDIMPRESSIONTOKEN/
            var hashedImpressionToken = pixelUrlParts[pixelUrlParts.length - 2]
            return hashedImpressionToken;
        };
    
        that.getTransitId = function(){
           if(typeof assoc_session_storage !== "undefined"){
                var existingTransitId = assoc_session_storage.get(TRANSIT_ID_KEY);
                return existingTransitId;
            } 
            return null;
        };
    
        that.getClickUrl = function() {
            return clickUrl;
        };
    
        that.addImpressionToken = function(linkElem, localSlotNum) {
            var finalSlotNum = getEffectiveSlotNum(localSlotNum);
            if(typeof impressionToken === "string" && impressionToken !== "") {
                addQueryParameter(linkElem, "imprToken", impressionToken);
                if(typeof finalSlotNum !== "undefined") addQueryParameter(linkElem, "slotNum", finalSlotNum);
            }
            return linkElem;
        };
    
        that.addImpressionTokenStr = function(url, localSlotNum) {
            var finalSlotNum = getEffectiveSlotNum(localSlotNum);
            if(typeof impressionToken === "string" && impressionToken !== "") {
                url = addQueryParameterStr(url, "imprToken", impressionToken);
                if(typeof finalSlotNum !== "undefined") url = addQueryParameterStr(url, "slotNum", finalSlotNum);
            }
            return url;
        };
    
        that.addTrackingParameters = function(linkElem, linkId, linkCode, trackingId, refMarker, creativeASIN, signature, signatureTimeStamp, adType, adMode, adFormat, impressionTimestamp) {
            return that.addSignature(
                that.addCreativeAsin(
                    that.addLinkId(
                        that.addTrackingId(
                            that.addSubtag(
                                that.addLinkCode(
                                    that.addRefRefUrl(
                                        that.addImpressionToken(
                                            that.addRefMarker(
                                                that.addAdType(
                                                    that.addAdMode(
                                                        that.addAdFormat(
                                                            that.addImpressionTimestamp(linkElem, impressionTimestamp),
                                                        adFormat),
                                                    adMode),
                                                adType),
                                            refMarker))),
                                    linkCode),
                                subtag),
                            trackingId),
                        linkId),
                    creativeASIN),
                signature, signatureTimeStamp
            );
        };
    
        that.addRefMarker = function(linkElem, refMarker) {
            var match, endsWithSlash = false;
            if(typeof refMarker === "undefined") return linkElem;
            if(match = linkElem.pathname.match(refMatch)) {
                linkElem.pathname = linkElem.pathname.replace(match[1], "ref=" + refMarker);
            } else {
                endsWithSlash = (linkElem.pathname.charAt(linkElem.pathname.length - 1) === '/');
                linkElem.pathname = linkElem.pathname + (endsWithSlash ? "" : "/") + "ref=" + refMarker;
            }
            return linkElem;
        };
    
        that.getRefMarker = function(linkElem) {
            var match;
            if(match = linkElem.pathname.match(refMatch)) {
                return match[1].substr(4);
            } else return undefined;
        };
    
        that.getCurrentURL = function(){
            return (window.location !== window.parent.location) ? document.referrer : document.location.href;
        }
    
        that.makeForesterCall = function(data) {
            var finalAAXPixelUrl = undefined, json;
            if(typeof JSON !== 'undefined') json = JSON.stringify(data);
            else if(typeof amzn_assoc_utils !== "undefined" && typeof amzn_assoc_utils["stringify"] === "function") json = amzn_assoc_utils.stringify(data);
            else return;
    
            if(typeof pixelUrl === "string") {
                finalAAXPixelUrl = pixelUrl + "?assoc_payload=" + encodeURIComponent(json);
                that.generateImage(finalAAXPixelUrl);
            }
        };
    
        that.recordImpression = function(linkCode, trackingId, data, skipIRCall, slotNum) {
            data["linkCode"] = linkCode;
            data["trackingId"] = trackingId;
            data["refUrl"] = that.getCurrentURL();
            if(disableABPCheck || !ABPPixelURL) {
                that.makeForesterCall(data);
            } else {
                that.addABPFlag(data, that.makeForesterCall);
            }
        };
    
        that.createAssocPayload = function(data, linkCode, trackingId, refUrl){
            data["linkCode"] = linkCode;
            data["trackingId"] = trackingId;
            data["refUrl"] = refUrl;
            var stringifiedData = "";
            if(typeof amzn_assoc_utils !== "undefined" && typeof amzn_assoc_utils["stringify"] === "function")
                stringifiedData = amzn_assoc_utils.stringify(data);
            return stringifiedData;
        }
    
        that.recordAESImpression = function(linkCode, trackingId, data){
            if(typeof AESPixelUrl === "string") {
                var assocPayload = that.createAssocPayload(data, linkCode, trackingId, that.getCurrentURL());
                var hashedImpressionToken = that.getHashedImpressionToken();
                var finalAESPixelUrl = AESPixelUrl + hashedImpressionToken+"/pixel?assoc_payload=" + encodeURIComponent(assocPayload);
                that.generateImage(finalAESPixelUrl);
            }
    
        };
    
        that.recordTransit = function(){
            //if transitId is not present or blog has utm_param in url, set new transitId
            if(!(that.getTransitId()) || that.isUTMParamPresentInUrl(that.getCurrentURL())){
                assoc_session_storage.set(TRANSIT_ID_KEY, that.generateTransitId());
            }
        }
    
        that.isUTMParamPresentInUrl = function(url){
            var utmParamExists = url.match(/utm_source=/i);
            return (utmParamExists !== null);
        }
    
    
        that.addAAXClickUrls = function(links){
            var aaxClickUrl, i, href;
            //convert all given links with AAX click urls
            if(typeof links === 'undefined' || typeof clickUrl === 'undefined') return;
    
            for (i = 0; i < links.length; i++) {
                href = String(links[i].href);
                if(href.indexOf(clickUrl) < 0) {
                    aaxClickUrl = clickUrl + href;
                    links[i].href = aaxClickUrl;
                }
            }
        };
    
        that.addAAXClickUrl = function(url){
            //append given url with AAX click url
            if(typeof url === 'undefined' || url.indexOf(clickUrl) === 0) return url;
            return clickUrl + url;
        };
    
        that.updateLinks = function(links, updaterFunc) {
            var i, href;
            if(typeof updaterFunc !== "function") return;
            for(i = 0; i < links.length; i++) {
                href = String(links[i].href);
                links[i].href = updaterFunc(href);
            }
        };
    
        that.elementInViewPort = function(el) {
            var rect = el.getBoundingClientRect(),
                inViewPort = (
                    rect.top >= 0 &&
                    rect.left >= 0 &&
                    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
                    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
                );
    
            return {
                "posX": rect.left + window.pageXOffset,
                "posY": rect.top + window.pageYOffset,
                "inViewPort": inViewPort
            };
        };
    
        that.recordViewability = function(slotNum, aboveTheFold, topPos, leftPos){
            if(typeof pixelUrl === "string") {
                var payload = that.createViewbilityPayload(slotNum, aboveTheFold, topPos, leftPos);
                var finalAAXPixelUrl = pixelUrl + payload + "&cb=" + (new Date()).getTime();
                that.generateImage(finalAAXPixelUrl);
            }
        };
        that.recordAESViewability = function(slotNum, aboveTheFold, topPos, leftPos){
            if(typeof AESPixelUrl === "string") {
                var payload = that.createViewbilityPayload(slotNum, aboveTheFold, topPos, leftPos);
                var hashedImpressionToken = that.getHashedImpressionToken();
                var encodedPayload = encodeURIComponent(payload);
                var finalAESPixelUrl = AESPixelUrl + hashedImpressionToken + "/pixel/"+encodedPayload
                                        + "&cb=" + (new Date()).getTime();
                that.generateImage(finalAESPixelUrl);
            }
        };
    
        that.createViewbilityPayload = function(slotNum, aboveTheFold, topPos, leftPos){
            var viewbilityAttr = {};
            if(typeof aboveTheFold !== "undefined") viewbilityAttr["above_the_fold"] = aboveTheFold;
            if(typeof topPos !== "undefined")       viewbilityAttr["topPos"] = topPos;
            if(typeof leftPos !== "undefined")      viewbilityAttr["leftPos"] = leftPos;
            if(typeof slotNum !== "undefined"){   
                //handling case when only slotnum is passed
                if (Object.keys(viewbilityAttr).length === 0)
                    viewbilityAttr["viewable"] = true;
    
                viewbilityAttr["slotNum"] = slotNum;
            }
    
            var stringifiedData = "";
            if(typeof amzn_assoc_utils !== "undefined" && typeof amzn_assoc_utils["stringify"] === "function")
                stringifiedData = amzn_assoc_utils.stringify({"adViewability":[viewbilityAttr]});
            return stringifiedData;
        };
    
        that.generateImage = function(imageSrc){
            if(typeof imageSrc !== "undefined")
                (new Image()).src = imageSrc;
        };
    
        that.addABPFlag = function(data, callback) {
            var detected = false,
            checksRemain = 2,
            img1 = document.body ? document.body.appendChild(new Image()) : new Image(),
            img2 = document.body ? document.body.appendChild(new Image()) : new Image(),
            error1 = false,
            error2 = false,
            random = Math.random() * 11,
            px = ABPPixelURL + "?ch=*&rn=*",
            beforeCheck = function(callback, timeout) {
                if (checksRemain === 0 || timeout > 1E3) {
                    data.supplySideMetadata = {
                        ABPInstalled: checksRemain === 0 && detected
                    };
                    callback(data);
                } else {
                    setTimeout(function() {
                        beforeCheck(callback, timeout * 2);
                    }, timeout * 2);
                }
            },
            checkImages = function() {
                if(--checksRemain)
                    return;
                detected = !error1 && error2;
            };
    
            img1.style.display = "none";
            img2.style.display = "none";
    
            img1.onload = checkImages;
            img1.onerror = function() {
                error1 = true;
                checkImages();
            };
            img1.src = px.replace(/\*/, 1).replace(/\*/, random);
    
            img2.onload = checkImages;
            img2.onerror = function() {
                error2 = true;
                checkImages();
            };
            img2.src = px.replace(/\*/, 2).replace(/\*/, random);
            beforeCheck(callback, 250);
        };
    
        return that;
    };
    if(typeof amzn_assoc_utils === "undefined") {
        amzn_assoc_utils = {};
    }
    
    
     
    </head>
    <body style="margin-bottom: 0px; margin-top: 0px;" class="vsc-initialized" marginwidth="0">
    
            <div id="amznBanners_assoc_banner_placement_default_${slotNum}_div">
                <img id="amznBanners_assoc_banner_placement_default_${slotNum}_img" usemap="#amznBanners_assoc_banner_placement_default_${slotNum}_boxmap" src="https://images-na.ssl-images-amazon.com/images/G/15/img15/biss/Associates/24569-CA-BISS-21Aug-frassco_160x600._V313078032_.png">
                    <map name="amznBanners_assoc_banner_placement_default_${slotNum}_boxmap">
                        <area id="amznBanners_assoc_banner_placement_default_${slotNum}_privacybox" shape="rect" coords="(0,588,160,600)" href="http://rcm-na.amazon-adsystem.com/e/cm/privacy-policy.html?o=15" target="_top" rel="nofollow">
                        <area id="amznBanners_assoc_banner_placement_default_${slotNum}_a" shape="rect" coords="0,0,10000,10000" href="https://www.amazon.ca/b?tag=suddenlysas06-20&amp;linkCode=ur1&amp;node=11076213011" target="_top" rel="nofollow">
                    </map>
            </div>
    
    
    
            <script type="text/javascript">
                amzn_assoc_ad_spec.isIframe = true;
                amzn_assoc_ad_spec.linkCode = "ur1";
                window.amznBannerAd(amzn_assoc_ad_spec).init();
                            var amazon_assoc_ir_f_call_associates_ads = function(map) {
                var logTypeStr = "", foresterURL, json;
                if(typeof JSON !== 'undefined') json = JSON.stringify(map);
                else if(typeof amzn_assoc_utils !== 'undefined') json = amzn_assoc_utils.stringify(map);
                else return;
                if(typeof map.logType !== "undefined") logTypeStr = "&logType=" + map.logType;
                // forester URLs are of format //<end_point>/<api_version>/<channel_id>/<channel_version>/<OPERATION>/
                // Depending on operation, we either pass the data in the URI or we pass them as query parameters
                // if operation is OP, data must be in query parameters while if operation is TOP,
                // data must be in the URI itself
                foresterURL = "//fls-na.amazon-adsystem.com/1/associates-ads/1/OP/r/json";
                                foresterURL = foresterURL + "?cb=" + (new Date()).getTime() + logTypeStr + "&p=" + encodeURIComponent(json);
                            (new Image()).src = foresterURL;
            };
        var amazon_assoc_ir_f_call = amazon_assoc_ir_f_call_associates_ads;
            </script>
        </body></iframe>

    Note: This won't detect plain visibility hiding (where they use injected styles to make them invisible instead of blocking and collapsing) or more sophisticated blocking, which will eventually happen if (probably a matter of "when") this devolves into an (anti-)adblocker arms race. It's a race you won't win, and will probably draw the ire of your users. Just being "nice" about it should hopefully mostly prevent this, and choosing ads that are mostly "unobtrusive" text based and emailing adblocker team to put you on the unobtrusive list is another option.
    But, and I emphasize this, it's your decision.