1// =============================================== 2// AdBlock detector 3// 4// Attempts to detect the presence of Ad Blocker software and notify listener of its existence. 5// Copyright (c) 2017 IAB 6// 7// The BSD-3 License 8// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 9// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 10// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13// =============================================== 14 15/** 16 * @name window.adblockDetector 17 * 18 * IAB Adblock detector. 19 * Usage: window.adblockDetector.init(options); 20 * 21 * Options object settings 22 * 23 * @prop debug: boolean 24 * Flag to indicate additional debug output should be printed to console 25 * 26 * @prop found: @function 27 * Callback function to fire if adblock is detected 28 * 29 * @prop notfound: @function 30 * Callback function to fire if adblock is not detected. 31 * NOTE: this function may fire multiple times and give false negative 32 * responses during a test until adblock is successfully detected. 33 * 34 * @prop complete: @function 35 * Callback function to fire once a round of testing is complete. 36 * The test result (boolean) is included as a parameter to callback 37 * 38 * example: window.adblockDetector.init( 39 { 40 found: function(){ ...}, 41 notFound: function(){...} 42 } 43 ); 44 * 45 * 46 */ 47 48"use strict"; 49(function(win) { 50 51 var version = '1.0'; 52 53 var ofs = 'offset', cl = 'client'; 54 var noop = function(){}; 55 56 var testedOnce = false; 57 var testExecuting = false; 58 59 var isOldIEevents = (win.addEventListener === undefined); 60 61 /** 62 * Options set with default options initialized 63 * 64 */ 65 var _options = { 66 loopDelay: 50, 67 maxLoop: 5, 68 debug: true, 69 found: noop, // function to fire when adblock detected 70 notfound: noop, // function to fire if adblock not detected after testing 71 complete: noop // function to fire after testing completes, passing result as parameter 72 } 73 74 function parseAsJson(data){ 75 var result, fnData; 76 try{ 77 result = JSON.parse(data); 78 } 79 catch(ex){
80 try{ 81 fnData = new Function("return " + data); 82 result = fnData(); 83 } 84 catch(ex){ 85 log('Failed secondary JSON parse', true); 86 } 87 } 88 89 return result; 90 } 91 92 /** 93 * Ajax helper object to download external scripts. 94 * Initialize object with an options object 95 * Ex: 96 { 97 url : 'http://example.org/url_to_download', 98 method: 'POST|GET', 99 success: callback_function, 100 fail: callback_function 101 } 102 */ 103 var AjaxHelper = function(opts){ 104 var xhr = new XMLHttpRequest(); 105 106 this.success = opts.success || noop; 107 this.fail = opts.fail || noop; 108 var me = this; 109 110 var method = opts.method || 'get'; 111 112 /** 113 * Abort the request 114 */ 115 this.abort = function(){ 116 try{ 117 xhr.abort(); 118 } 119 catch(ex){ 120 } 121 } 122 123 function stateChange(vals){ 124 if(xhr.readyState == 4){ 125 if(xhr.status == 200){ 126 me.success(xhr.response); 127 } 128 else{ 129 // failed 130 me.fail(xhr.status); 131 } 132 } 133 } 134 135 xhr.onreadystatechange = stateChange; 136 137 function start(){ 138 xhr.open(method, opts.url, true); 139 xhr.send(); 140 } 141 142 start(); 143 } 144 145 /** 146 * Object tracking the various block lists 147 */ 148 var BlockListTracker = function(){ 149 var me = this; 150 var externalBlocklistData = {}; 151 152 /** 153 * Add a new external URL to track 154 */ 155 this.addUrl = function(url){ 156 externalBlocklistData[url] = { 157 url: url, 158 state: 'pending', 159 format: null, 160 data: null, 161 result: null 162 } 163 164 return externalBlocklistData[url]; 165 } 166 167 /** 168 * Loads a block list definition 169 */ 170 this.setResult = function(urlKey, state, data){ 171 var obj = externalBlocklistData[urlKey]; 172 if(obj == null){ 173 obj = this.addUrl(urlKey); 174 } 175 176 obj.state = state; 177 if(data == null){ 178 obj.result = null; 179 return; 180 } 181 182 if(typeof data === 'string'){ 183 try{ 184 data = parseAsJson(data); 185 obj.format = 'json'; 186 } 187 catch(ex){ 188 obj.format = 'easylist'; 189 // parseEasyList(data); 190 } 191 } 192 obj.data = data; 193 194 return obj; 195 } 196 197 } 198 199 var listeners = []; // event response listeners 200 var baitNode = null; 201 var quickBait = { 202 cssClass: 'pub_300x250 pub_300x250m pub_728x90 text-ad textAd text_ad text_ads text-ads text-ad-links' 203 }; 204 var baitTriggers = { 205 nullProps: [ofs + 'Parent'], 206 zeroProps: [] 207 }; 208 209 baitTriggers.zeroProps = [ 210 ofs +'Height', ofs +'Left', ofs +'Top', ofs +'Width', ofs +'Height', 211 cl + 'Height', cl + 'Width' 212 ]; 213 214 // result object 215 var exeResult = { 216 quick: null, 217 remote: null 218 }; 219 220 var findResult = null; // result of test for ad blocker 221 222 var timerIds = { 223 test: 0, 224 download: 0 225 }; 226 227 function isFunc(fn){ 228 return typeof(fn) == 'function'; 229 } 230 231 /** 232 * Make a DOM element 233 */ 234 function makeEl(tag, attributes){ 235 var k, v, el, attr = attributes; 236 var d = document; 237 238 el = d.createElement(tag); 239 240 if(attr){ 241 for(k in attr){ 242 if(attr.hasOwnProperty(k)){ 243 el.setAttribute(k, attr[k]); 244 } 245 } 246 } 247 248 return el; 249 } 250 251 function attachEventListener(dom, eventName, handler){ 252 if(isOldIEevents){ 253 dom.attachEvent('on' + eventName, handler); 254 } 255 else{ 256 dom.addEventListener(eventName, handler, false); 257 } 258 } 259 260 function log(message, isError){ 261 if(!_options.debug && !isError){ 262 return; 263 } 264 if(win.console && win.console.log){ 265 if(isError){ 266 console.error('[ABD] ' + message); 267 } 268 else{ 269 console.log('[ABD] ' + message); 270 } 271 } 272 } 273 274 var ajaxDownloads = []; 275 276 /** 277 * Load and execute the URL inside a closure function 278 */ 279 function loadExecuteUrl(url){ 280 var ajax, result; 281 282 blockLists.addUrl(url); 283 // setup call for remote list 284 ajax = new AjaxHelper( 285 { 286 url: url, 287 success: function(data){ 288 log('downloaded file ' + url); // todo - parse and store until use 289 result = blockLists.setResult(url, 'success', data); 290 try{ 291 var intervalId = 0, 292 retryCount = 0; 293 294 var tryExecuteTest = function(listData){ 295 if(!testExecuting){ 296 beginTest(listData, true); 297 return true; 298 } 299 return false;
300 } 301 302 if(findResult == true){ 303 return; 304 } 305 306 if(tryExecuteTest(result.data)){ 307 return; 308 } 309 else{ 310 log('Pause before test execution'); 311 intervalId = setInterval(function(){ 312 if(tryExecuteTest(result.data) || retryCount++ > 5){ 313 clearInterval(intervalId); 314 } 315 }, 250); 316 } 317 } 318 catch(ex){ 319 log(ex.message + ' url: ' + url, true); 320 } 321 }, 322 fail: function(status){ 323 log(status, true); 324 blockLists.setResult(url, 'error', null); 325 } 326 }); 327 328 ajaxDownloads.push(ajax); 329 } 330 331 332 /** 333 * Fetch the external lists and initiate the tests 334 */ 335 function fetchRemoteLists(){ 336 var i, url; 337 var opts = _options; 338 339 for(i=0;i<opts.blockLists.length;i++){ 340 url = opts.blockLists[i]; 341 loadExecuteUrl(url); 342 } 343 } 344 345 function cancelRemoteDownloads(){ 346 var i, aj; 347 348 for(i=ajaxDownloads.length-1;i >= 0;i--){ 349 aj = ajaxDownloads.pop(); 350 aj.abort(); 351 } 352 } 353 354 355 // ============================================================================= 356 /** 357 * Begin execution of the test 358 */ 359 function beginTest(bait){ 360 log('start beginTest'); 361 if(findResult == true){ 362 return; // we found it. don't continue executing 363 } 364 testExecuting = true; 365 castBait(bait); 366 367 exeResult.quick = 'testing'; 368 369 timerIds.test = setTimeout( 370 function(){ reelIn(bait, 1); }, 371 5); 372 } 373 374 /** 375 * Create the bait node to see how the browser page reacts 376 */ 377 function castBait(bait){ 378 var i, d = document, b = d.body; 379 var t; 380 var baitStyle = 'width: 1px !important; height: 1px !important; position: absolute !important; left: -10000px !important; top: -1000px !important;' 381 382 if(bait == null || typeof(bait) == 'string'){ 383 log('invalid bait being cast'); 384 return; 385 } 386 387 if(bait.style != null){ 388 baitStyle += bait.style; 389 } 390 391 baitNode = makeEl('div', { 392 'class': bait.cssClass, 393 'style': baitStyle 394 }); 395 396 log('adding bait node to DOM'); 397 398 b.appendChild(baitNode); 399 400 // touch these properties 401 for(i=0;i<baitTriggers.nullProps.length;i++){ 402 t = baitNode[baitTriggers.nullProps[i]]; 403 } 404 for(i=0;i<baitTriggers.zeroProps.length;i++){ 405 t = baitNode[baitTriggers.zeroProps[i]]; 406 } 407 } 408 409 /** 410 * Run tests to see if browser has taken the bait and blocked the bait element 411 */ 412 function reelIn(bait, attemptNum){ 413 var i, k, v; 414 var body = document.body; 415 var found = false; 416 417 if(baitNode == null){ 418 log('recast bait'); 419 castBait(bait || quickBait); 420 } 421 422 if(typeof(bait) == 'string'){ 423 log('invalid bait used', true); 424 if(clearBaitNode()){ 425 setTimeout(function(){ 426 testExecuting = false; 427 }, 5); 428 } 429 430 return; 431 } 432 433 if(timerIds.test > 0){ 434 clearTimeout(timerIds.test); 435 timerIds.test = 0; 436 } 437 438 // test for issues 439 440 if(body.getAttribute('abp') !== null){ 441 log('found adblock body attribute'); 442 found = true; 443 } 444 445 for(i=0;i<baitTriggers.nullProps.length;i++){ 446 if(baitNode[baitTriggers.nullProps[i]] == null){ 447 if(attemptNum>4) 448 found = true; 449 log('found adblock null attr: ' + baitTriggers.nullProps[i]); 450 break; 451 } 452 if(found == true){ 453 break; 454 } 455 } 456 457 for(i=0;i<baitTriggers.zeroProps.length;i++){ 458 if(found == true){ 459 break; 460 } 461 if(baitNode[baitTriggers.zeroProps[i]] == 0){ 462 if(attemptNum>4) 463 found = true; 464 log('found adblock zero attr: ' + baitTriggers.zeroProps[i]); 465 } 466 } 467 468 if(window.getComputedStyle !== undefined) { 469 var baitTemp = window.getComputedStyle(baitNode, null); 470 if(baitTemp.getPropertyValue('display') == 'none' 471 || baitTemp.getPropertyValue('visibility') == 'hidden') { 472 if(attemptNum>4) 473 found = true; 474 log('found adblock computedStyle indicator'); 475 } 476 } 477 478 testedOnce = true; 479 480 if(found || attemptNum++ >= _options.maxLoop){ 481 findResult = found; 482 log('exiting test loop - value: ' + findResult); 483 notifyListeners(); 484 if(clearBaitNode()){ 485 setTimeout(function(){ 486 testExecuting = false;
487 }, 5); 488 } 489 } 490 else{ 491 timerIds.test = setTimeout(function(){ 492 reelIn(bait, attemptNum); 493 }, _options.loopDelay); 494 } 495 } 496 497 function clearBaitNode(){ 498 if(baitNode === null){ 499 return true; 500 } 501 502 try{ 503 if(isFunc(baitNode.remove)){ 504 baitNode.remove(); 505 } 506 document.body.removeChild(baitNode); 507 } 508 catch(ex){ 509 } 510 baitNode = null; 511 512 return true; 513 } 514 515 /** 516 * Halt the test and any pending timeouts 517 */ 518 function stopFishing(){ 519 if(timerIds.test > 0){ 520 clearTimeout(timerIds.test); 521 } 522 if(timerIds.download > 0){ 523 clearTimeout(timerIds.download); 524 } 525 526 cancelRemoteDownloads(); 527 528 clearBaitNode(); 529 } 530 531 /** 532 * Fire all registered listeners 533 */ 534 function notifyListeners(){ 535 var i, funcs; 536 if(findResult === null){ 537 return; 538 } 539 for(i=0;i<listeners.length;i++){ 540 funcs = listeners[i]; 541 try{ 542 if(funcs != null){ 543 if(isFunc(funcs['complete'])){ 544 funcs['complete'](findResult); 545 } 546 547 if(findResult && isFunc(funcs['found'])){ 548 funcs['found'](); 549 } 550 else if(findResult === false && isFunc(funcs['notfound'])){ 551 funcs['notfound'](); 552 } 553 } 554 } 555 catch(ex){ 556 log('Failure in notify listeners ' + ex.Message, true); 557 } 558 } 559 } 560 561 /** 562 * Attaches event listener or fires if events have already passed. 563 */ 564 function attachOrFire(){ 565 var fireNow = false; 566 var fn; 567 568 if(document.readyState){ 569 if(document.readyState == 'complete'){ 570 fireNow = true; 571 } 572 } 573 574 fn = function(){ 575 beginTest(quickBait, false); 576 } 577 578 if(fireNow){ 579 fn(); 580 } 581 else{ 582 attachEventListener(win, 'load', fn); 583 } 584 } 585 586 587 var blockLists; // tracks external block lists 588 589 /** 590 * Public interface of adblock detector 591 */ 592 var impl = { 593 /** 594 * Version of the adblock detector package 595 */ 596 version: version, 597 598 /** 599 * Initialization function. See comments at top for options object 600 */ 601 init: function(options){ 602 var k, v, funcs; 603 604 if(!options){ 605 return; 606 } 607 608 funcs = { 609 complete: noop, 610 found: noop, 611 notfound: noop 612 }; 613 614 for(k in options){ 615 if(options.hasOwnProperty(k)){ 616 if(k == 'complete' || k == 'found' || k == 'notFound'){ 617 funcs[k.toLowerCase()] = options[k]; 618 } 619 else{ 620 _options[k] = options[k]; 621 } 622 } 623 } 624 625 listeners.push(funcs); 626 627 blockLists = new BlockListTracker(); 628 629 attachOrFire(); 630 } 631 } 632 633 win['adblockDetector'] = impl; 634 635})(window)
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.