From b4fc02d28654ef61eb30d1df27afe7896f0bb467 Mon Sep 17 00:00:00 2001 From: Elbert Alias <77259+AliasIO@users.noreply.github.com> Date: Wed, 2 Dec 2020 17:28:06 +1100 Subject: [PATCH] Fix robots.txt --- src/drivers/webextension/js/driver.js | 4 +- src/drivers/webextension/js/lib/iframe.js | 3052 ++++++++++---------- src/drivers/webextension/js/lib/network.js | 1583 +++++----- 3 files changed, 2375 insertions(+), 2264 deletions(-) diff --git a/src/drivers/webextension/js/driver.js b/src/drivers/webextension/js/driver.js index 1baa5794c..e334abd35 100644 --- a/src/drivers/webextension/js/driver.js +++ b/src/drivers/webextension/js/driver.js @@ -551,7 +551,7 @@ const Driver = { !(await getOption('tracking', true)) || hostnameIgnoreList.test(hostname) ) { - return + return [] } if (typeof Driver.cache.robots[hostname] !== 'undefined') { @@ -703,6 +703,8 @@ const Driver = { if (Driver.cache.ads.length > 1) { await Driver.post('https://ad.wappalyzer.com/log/wp/', Driver.cache.ads) + + Driver.cache.ads = [] } } }, diff --git a/src/drivers/webextension/js/lib/iframe.js b/src/drivers/webextension/js/lib/iframe.js index d36e22baa..eb910cc93 100644 --- a/src/drivers/webextension/js/lib/iframe.js +++ b/src/drivers/webextension/js/lib/iframe.js @@ -1,168 +1,168 @@ 'use strict' -;(function(win) { - var exports = {} - - ;(function(exports) { - var utils = { - /** - * Normalize URL - * @param {String} url - */ - normalizeUrl: function(url) { - return this.hashUrl(url) || null - }, - - /** - * Get referrer. - */ - getReferrer: function() { - return this.normalizeUrl(document.referrer) - }, - - /** - * Get current page URL. - */ - getPageUrl: function() { - return this.normalizeUrl(window.location.href) - }, - - /** - * Generated hashed URL. - * @param {String} url - */ - hashUrl: function(url) { - var a, result - - if (!url || url.indexOf('http') !== 0) { - return null - } - - a = document.createElement('a') - a.href = url - - result = a.protocol + '//' + a.hostname + '/' - - if (a.pathname && a.pathname !== '/') { - result += this.hashCode(a.pathname) - } - - if (a.search) { - result += '?' + this.hashCode(a.search) - } - - if (a.hash) { - result += '#' + this.hashCode(a.hash) - } - - return result - }, - - /** - * Generate random hash. - * @param {String} str - */ - hashCode: function(str) { - var hash = 0, - kar, - i - - if (str.length === 0) { - return hash - } - - for (i = 0; i < str.length; i++) { - kar = str.charCodeAt(i) - hash = (hash << 5) - hash + kar - hash = hash & hash - } - - return hash + Math.pow(2, 32) - }, - - /** - * Apply array function to non-array. - * @param {Object} a - */ - realArray: function(a) { - return Array.prototype.slice.apply(a) - }, - - /** - * Listener callback for onDocLoaded. - * @param {Object} doc - * @param {Function} callback - */ - onDocLoaded: function(doc, callback) { - if (doc.readyState === 'loading') { - doc.addEventListener('DOMContentLoaded', callback) - } else { - callback() - } - }, - - SCRIPT_IN_WINDOW_TOP: window === window.top, - - /** - * Check for href Window object. - * @param {Object} win - */ - isFriendlyWindow: function(win) { - var href - try { - href = win.location.href - } catch (e) { - return false - } - return true - }, - - /** - * Get default view from element. - * @param {Object} el - */ - elementWindow: function(el) { - return el.ownerDocument.defaultView - }, - - /** - * Get viewport size. - * @param {Object} win - */ - viewport: function(win) { - return { width: win.innerWidth, height: win.innerHeight } - }, - - /** - * Parse query string parameters. - * @param {String} qs - */ - parseQS: function(qs) { - if (qs.indexOf('http') === 0) { - qs = qs.split('?')[1] - } - var i, kvs, key, val - var dict = {} - qs = qs.split('&') - for (i = 0; i < qs.length; i++) { - kvs = qs[i].split('=') - key = kvs[0] - val = kvs.slice(1).join('=') - try { - dict[key] = window.decodeURIComponent(val) - } catch (e) { - continue - } - } - return dict - }, - - /** - * Send PostMessage response. - * @param {Object} message - * @param {String} event - * @param {String} responseMessage - */ - sendToBackground: function(message, event, responseMessage) { +;(function (win) { + const exports = {} + + ;(function (exports) { + const utils = { + /** + * Normalize URL + * @param {String} url + */ + normalizeUrl(url) { + return this.hashUrl(url) || null + }, + + /** + * Get referrer. + */ + getReferrer() { + return this.normalizeUrl(document.referrer) + }, + + /** + * Get current page URL. + */ + getPageUrl() { + return this.normalizeUrl(window.location.href) + }, + + /** + * Generated hashed URL. + * @param {String} url + */ + hashUrl(url) { + let a, result + + if (!url || url.indexOf('http') !== 0) { + return null + } + + a = document.createElement('a') + a.href = url + + result = a.protocol + '//' + a.hostname + '/' + + if (a.pathname && a.pathname !== '/') { + result += this.hashCode(a.pathname) + } + + if (a.search) { + result += '?' + this.hashCode(a.search) + } + + if (a.hash) { + result += '#' + this.hashCode(a.hash) + } + + return result + }, + + /** + * Generate random hash. + * @param {String} str + */ + hashCode(str) { + let hash = 0 + let kar + let i + + if (str.length === 0) { + return hash + } + + for (i = 0; i < str.length; i++) { + kar = str.charCodeAt(i) + hash = (hash << 5) - hash + kar + hash = hash & hash + } + + return hash + Math.pow(2, 32) + }, + + /** + * Apply array function to non-array. + * @param {Object} a + */ + realArray(a) { + return Array.prototype.slice.apply(a) + }, + + /** + * Listener callback for onDocLoaded. + * @param {Object} doc + * @param {Function} callback + */ + onDocLoaded(doc, callback) { + if (doc.readyState === 'loading') { + doc.addEventListener('DOMContentLoaded', callback) + } else { + callback() + } + }, + + SCRIPT_IN_WINDOW_TOP: window === window.top, + + /** + * Check for href Window object. + * @param {Object} win + */ + isFriendlyWindow(win) { + let href + try { + href = win.location.href + } catch (e) { + return false + } + return true + }, + + /** + * Get default view from element. + * @param {Object} el + */ + elementWindow(el) { + return el.ownerDocument.defaultView + }, + + /** + * Get viewport size. + * @param {Object} win + */ + viewport(win) { + return { width: win.innerWidth, height: win.innerHeight } + }, + + /** + * Parse query string parameters. + * @param {String} qs + */ + parseQS(qs) { + if (qs.indexOf('http') === 0) { + qs = qs.split('?')[1] + } + let i, kvs, key, val + const dict = {} + qs = qs.split('&') + for (i = 0; i < qs.length; i++) { + kvs = qs[i].split('=') + key = kvs[0] + val = kvs.slice(1).join('=') + try { + dict[key] = window.decodeURIComponent(val) + } catch (e) { + continue + } + } + return dict + }, + + /** + * Send PostMessage response. + * @param {Object} message + * @param {String} event + * @param {String} responseMessage + */ + sendToBackground(message, event, responseMessage) { chrome.runtime.sendMessage(message, (message) => { if (message && typeof message.tracking_enabled !== 'undefined') { if (message.tracking_enabled) { @@ -172,1373 +172,1369 @@ } } }) - }, - - /** - * Check if anonymous tracking is enabled. - * @param {Function} callback - * @param {Function} elseCallback - * @todo validate if utilCallback or utilElseCallback are being used. - */ - askIfTrackingEnabled: function(callback, elseCallback) { - utilCallback = callback - utilElseCallback = elseCallback - - this.sendToBackground( - 'is_tracking_enabled', - '', - 'tracking_enabled_response' - ) - } - } - - utils.SCRIPT_IN_FRIENDLY_IFRAME = - !utils.SCRIPT_IN_WINDOW_TOP && utils.isFriendlyWindow(window.parent) - utils.SCRIPT_IN_HOSTILE_IFRAME = - !utils.SCRIPT_IN_WINDOW_TOP && !utils.SCRIPT_IN_FRIENDLY_IFRAME - - /** - * Generate new Logging object. - */ - function LogGenerator() { - this.msgNum = 0 - this.pageMeta = { - url: utils.getPageUrl(), - isHP: window.location.pathname === '/', - referrer: utils.getReferrer(), - rand: Math.floor(Math.random() * 10e12), - startTime: new Date().getTime() - } - } - - LogGenerator.prototype = { - /** - * Log data. - * @param {String} event - * @param {Array} opt_assets - * @param {Array} opt_pageTags - */ - log: function(event, opt_assets, opt_pageTags) { - var opt_video_assets - if (event === 'video' || event === 'invalid-video') { - opt_video_assets = opt_assets || [] - opt_assets = [] - } else { - opt_video_assets = [] - opt_assets = opt_assets || [] - } - var result = { - doc: this.pageMeta, - event: event, - video_assets: opt_video_assets, - assets: opt_assets, - version: '3', - mrev: '15a9f21-d', - msgNum: this.msgNum, - timestamp: new Date().getTime(), - pageVis: document.visibilityState, - pageFoc: document.hasFocus(), - pageTags: opt_pageTags || [] - } - this.msgNum++ - return result - } - } - - utils.LogGenerator = LogGenerator - - let utilCallback, utilElseCallback - - exports.utils = utils - })(exports) - ;(function(exports) { - var SizeMatcher = { - VALID_AD_SIZES: [ - [300, 50], - [320, 50], - [160, 600], - [300, 250], - [300, 600], - [300, 1050], - [336, 280], - [336, 850], - [468, 60], - [728, 90], - [728, 250], - [728, 270], - [970, 66], - [970, 90], - [970, 125], - [970, 250], - [970, 400], - [970, 415], - [1280, 100] - ], - - PX_SIZE_TOL: 10, - - /** - * Get ad size. - * @param {Int} width - * @param {Int} height - */ - getMatchedAdSize: function(width, height) { - if (!this.set) { - this.set = this._makeSizeSet() - } - - return this.set[Math.round(width) + 'x' + Math.round(height)] - }, - - /** - * Check element size. - * @param {HTMLElement} el - */ - elementIsAdShaped: function(el) { - return !!this.getMatchedAdSizeForElement(el) - }, - - /** - * Get ad size. - * @param {HTMLElement} el - * @todo better description - */ - getMatchedAdSizeForElement: function(el) { - var rect = el.getBoundingClientRect() - return this.getMatchedAdSize(rect.width, rect.height) - }, - - /** - * Generate ad sizes. - */ - _makeSizeSet: function() { - var set = {} - var i - var xfuz - var yfuz - var size - var width - var height - - for (i = 0; i < this.VALID_AD_SIZES.length; i++) { - for (xfuz = -this.PX_SIZE_TOL; xfuz <= this.PX_SIZE_TOL; xfuz++) { - for (yfuz = -this.PX_SIZE_TOL; yfuz <= this.PX_SIZE_TOL; yfuz++) { - size = this.VALID_AD_SIZES[i] - width = size[0] + xfuz - height = size[1] + yfuz - set[width + 'x' + height] = size - } - } - } - return set - } - } - - var Throttler = { - MAX_SEARCHES_PER_WINDOW: 10, - MAX_SEARCHES_PER_ELEMENT: 2, - - /** - * Count number of elements. - * @param {HTMLElement} el - */ - countSearch: (el) => { - if (typeof el.searches !== 'number') { - el.searches = 0 - } - - el.searches += 1 - }, - - /** - * - * @param {*} el - * @param {*} max - * - * @todo add description - */ - throttle: function(el, max) { - if (typeof el.searches === 'number' && el.searches >= max) { - return true - } - return false - }, - - /** - * - * @param {*} el - * - * @todo add description - */ - throttleElement: function(el) { - return this.throttle(el, this.MAX_SEARCHES_PER_ELEMENT) - }, - - /** - * - * @param {*} win - * - * @todo add description - */ - throttleWin: function(win) { - return this.throttle(win, this.MAX_SEARCHES_PER_WINDOW) - }, - - /** - * - * @param {*} el - * - * @todo add description - */ - getCount: function(el) { - return el.searches || 0 - } - } - - /** - * Initialize window and document elements. - * @param {*} win - */ - function TopSearcher(win) { - this.win = win - this.doc = win.document - } - - /** - * Add search function. - */ - TopSearcher.prototype.search = function() { - var candidates = exports.utils.realArray( - this.doc.querySelectorAll('img, object, embed') - ), - html5Ad, - ads = [] - - ads = ads.concat( - candidates.filter(function(el) { - if (!el.mpAdFound && !Throttler.throttleElement(el)) { - Throttler.countSearch(el) - if ( - (el.tagName !== 'IMG' || isStandardImage(el)) && - SizeMatcher.elementIsAdShaped(el) - ) { - el.mpAdFound = true - return true - } - } - return false - }) - ) - - html5Ad = this._mainGetHTMLAd() - if (html5Ad) { - html5Ad.html5 = true - html5Ad.mpAdFound = true - ads.push(html5Ad) - } - - return ads - } - - /** - * @todo add description - */ - TopSearcher.prototype._mainGetHTMLAd = function() { - var styles = this.doc.querySelectorAll( - 'div > style, div > link[rel="stylesheet"]' - ), - i, - div - for (i = 0; i < styles.length; i++) { - div = styles[i].parentNode - if ( - !div.mpAdFound && - SizeMatcher.elementIsAdShaped(div) && - this._jumpedOut(div) - ) { - return div - } - } - } - - /** - * @todo add description - */ - TopSearcher.prototype._jumpedOut = function(el) { - var siblings, ifrs - siblings = exports.utils.realArray(el.parentNode.children) - ifrs = siblings.filter(function(el) { - return ( - el.tagName === 'IFRAME' && - el.offsetWidth === 0 && - el.offsetHeight === 0 - ) - }) - return ifrs.length > 0 - } - - /** - * - * @param {*} win - * - * @todo add description - */ - function IframeSearcher(win) { - this.MIN_AD_AREA = 14000 - this.MIN_WINDOW_PX = 10 - - this.win = win - this.doc = win.document - this.body = win.document.body - this.winClickTag = win.clickTag - this.adSizeMeta = this._getAdSizeMeta() - this.numElementsInBody = - (this.body && this.body.querySelectorAll('*').length) || 0 - - this.shouldSearchWindow = false - if ( - !this.win.mpAdFound && - this.body && - !Throttler.throttleWin(this.win) - ) { - this.winWidth = this.win.innerWidth - this.winHeight = this.win.innerHeight - if ( - this._meetsMinAdSize(this.winWidth, this.winHeight) && - !this._containsLargeIframes() - ) { - this.shouldSearchWindow = true - } - } - } - - /** - * @todo add description - */ - IframeSearcher.prototype.search = function() { - var ad - - if (this.shouldSearchWindow) { - ad = this._search() - if (ad) { - ad.mpAdFound = true - win.mpAdFound = true - return ad - } - Throttler.countSearch(this.win) - } - - return null - } - - /** - * @todo add description - */ - IframeSearcher.prototype._search = function() { - var _this = this, - stdCandidates, - html5Candidates, - stdEl, - html5El - - stdCandidates = this.body.querySelectorAll('img, object, embed') - - stdEl = getFirst(stdCandidates, function(el) { - if ( - !el.mpAdFound && - !Throttler.throttleElement(el) && - (el.tagName !== 'IMG' || isStandardImage(el)) && - _this._elementIsAtLeastAsBigAsWindow(el) - ) { - return true - } - Throttler.countSearch(el) - return false - }) - - if (stdEl) { - return stdEl - } - - if (this._isHTML5Iframe()) { - html5Candidates = this.doc.querySelectorAll( - 'body, canvas, button, video, svg, div' - ) - html5El = getFirst(html5Candidates, function(el) { - if (_this._elementIsAtLeastAsBigAsWindow(el)) { - return true - } - Throttler.countSearch(el) - return false - }) - } - - if (html5El) { - html5El.html5 = true - html5El.winClickTag = this.winClickTag - html5El.adSizeMeta = this.adSizeMeta - return html5El - } - - return null - } - - /** - * @todo add description - */ - IframeSearcher.prototype._isHTML5Iframe = function() { - if (this.winClickTag || this.adSizeMeta) { - return true - } - - if ( - this.doc.querySelectorAll('canvas', 'button', 'video', 'svg').length > 0 - ) { - return true - } - - if ( - this.numElementsInBody >= 5 && - Throttler.getCount(this.win) > 0 && - this.doc.querySelectorAll('div').length > 0 - ) { - return true - } - - return false - } - - /** - * @todo add description - */ - IframeSearcher.prototype._elementIsAtLeastAsBigAsWindow = function(el) { - var rect = el.getBoundingClientRect(), - tol = 0.95 - - return ( - rect.width >= tol * this.winWidth && rect.height >= tol * this.winHeight - ) - } - - /** - * @todo add description - */ - IframeSearcher.prototype._meetsMinAdSize = function(width, height) { - return width * height >= this.MIN_AD_AREA - } - - /** - * @todo add description - */ - IframeSearcher.prototype._containsLargeIframes = function() { - var iframes = this.doc.querySelectorAll('iframe') - var rect - var i - for (i = 0; i < iframes.length; i++) { - rect = iframes[i].getBoundingClientRect() - if ( - rect.width > this.MIN_WINDOW_PX || - rect.height > this.MIN_WINDOW_PX - ) { - return true - } - } - return false - } - - /** - * @todo add description - */ - IframeSearcher.prototype._getAdSizeMeta = function() { - var adSizeMeta = this.doc.querySelectorAll('meta[name="ad.size"]') - if (adSizeMeta.length > 0) { - return adSizeMeta[0].content - } else { - return null - } - } - - /** - * - * @param {*} arr - * @param {*} testFn - * - * @todo add description - */ - function getFirst(arr, testFn) { - var i, el - for (i = 0; i < arr.length; i++) { - el = arr[i] - if (testFn(el)) { - return el - } - } - return null - } - - /** - * Check for image attributes. - * @param {HTMLElement} img - */ - function isStandardImage(img) { - return ( - img.src && - (img.parentNode.tagName === 'A' || img.getAttribute('onclick')) - ) - } - - /** - * Extract iFrames from page. - * @param {Object} win - */ - function getFriendlyIframes(win) { - var iframes = win.document.querySelectorAll('iframe') - iframes = exports.utils.realArray(iframes) - var friendlyIframes = iframes.filter(function(ifr) { - return exports.utils.isFriendlyWindow(ifr.contentWindow) - }) - return friendlyIframes - } - - /** - * - * @param {*} win - */ - function findAds(win) { - var i, - iframes, - searcher, - ad, - ads = [] - - if (win === win.top) { - searcher = new TopSearcher(win) - ads = ads.concat(searcher.search()) - } else { - searcher = new IframeSearcher(win) - ad = searcher.search() - if (ad) { - ads.push(ad) - } - } - - iframes = getFriendlyIframes(win) - for (i = 0; i < iframes.length; i++) { - ads = ads.concat(findAds(iframes[i].contentWindow)) - } - - return ads - } - - exports.adfinder = { - getMatchedAdSize: SizeMatcher.getMatchedAdSize.bind(SizeMatcher), - findAds: findAds - } - })(exports) - ;(function(exports) { - var parser = { - TAGS_WITH_SRC_ATTR: { - IMG: true, - SCRIPT: true, - IFRAME: true, - EMBED: true - }, - - MAX_ATTR_LEN: 100, - - /** - * - * @param {*} el - * @param {*} params - * - * @todo add description - */ - getUrl: function(el, params) { - var url - - if (this.TAGS_WITH_SRC_ATTR.hasOwnProperty(el.tagName)) { - url = el.src - } else if (el.tagName === 'OBJECT') { - url = el.data || (params && params.movie) || null - } else if (el.tagName === 'A') { - url = el.href - } - - if (url && url.indexOf('http') === 0) { - return url - } else { - return null - } - }, - - /** - * - * @param {*} el - * - * @todo add description - */ - getParams: function(el) { - if (el.tagName !== 'OBJECT') { - return null - } - - var i, child - var params = {} - var children = el.children - for (i = 0; i < children.length; i++) { - child = children[i] - if (child.tagName === 'PARAM' && child.name) { - params[child.name.toLowerCase()] = child.value - } - } - return params - }, - - /** - * Get element position. - * @param {HTMLElement} el - */ - getPosition: function(el) { - var rect = el.getBoundingClientRect() - var win = exports.utils.elementWindow(el) - - return { - width: Math.round(rect.width), - height: Math.round(rect.height), - left: Math.round(rect.left + win.pageXOffset), - top: Math.round(rect.top + win.pageYOffset) - } - }, - - /** - * - * @param {*} el - * @param {*} params - * @param {*} url - * - * @todo add description - */ - getFlashvars: function(el, params, url) { - var flashvars - var urlQS = url && url.split('?')[1] - - if (el.tagName === 'EMBED') { - flashvars = el.getAttribute('flashvars') || urlQS - } else if (el.tagName === 'OBJECT') { - flashvars = params.flashvars || el.getAttribute('flashvars') || urlQS - } - - return (flashvars && exports.utils.parseQS(flashvars)) || null - }, - - /** - * - * @param {*} el - * @param {*} flashvars - * - * @todo add description - */ - findClickThru: function(el, flashvars) { - var key - if (el.tagName === 'IMG' && el.parentElement.tagName === 'A') { - return el.parentElement.href - } else if (flashvars) { - for (key in flashvars) { - if (flashvars.hasOwnProperty(key)) { - if (key.toLowerCase().indexOf('clicktag') === 0) { - return flashvars[key] - } - } - } - } - return null - }, - - /** - * Get element attribute. - * @param {HTMLElement} el - * @param {String} name - */ - getAttr: function(el, name) { - var val = el.getAttribute(name) - - if (val && val.slice && val.toString) { - return val.slice(0, this.MAX_ATTR_LEN).toString() - } else { - return null - } - }, - - /** - * - * @param {*} obj - * @param {*} name - * @param {*} val - * - * @todo add description - */ - putPropIfExists: function(obj, name, val) { - if (val) { - obj[name] = val - } - }, - - /** - * - * @param {*} obj - * @param {*} el - * @param {*} name - * - * @todo add description - */ - putAttrIfExists: function(obj, el, name) { - var val = this.getAttr(el, name) - this.putPropIfExists(obj, name, val) - }, - - /** - * Convert Element to JSON - * @param {HTMLElement} el - * @param {Boolean} opt_findClickThru - */ - elementToJSON: function(el, opt_findClickThru) { - var pos = this.getPosition(el) - var params = this.getParams(el) - var url = this.getUrl(el, params) - var flashvars = this.getFlashvars(el, params, url) - var clickThru = opt_findClickThru && this.findClickThru(el, flashvars) - var json = { - tagName: el.tagName, - width: pos.width, - height: pos.height, - left: pos.left, - top: pos.top, - children: [] - } - - if (params) { - delete params.flashvars - } - - this.putAttrIfExists(json, el, 'id') - this.putAttrIfExists(json, el, 'class') - this.putAttrIfExists(json, el, 'name') - - this.putPropIfExists(json, 'flashvars', flashvars) - this.putPropIfExists(json, 'url', url) - this.putPropIfExists(json, 'params', params) - this.putPropIfExists(json, 'clickThru', clickThru) - - return json - } - } - - exports.parser = { elementToJSON: parser.elementToJSON.bind(parser) } - })(exports) - - // Anonymous invocation. - ;(function(exports) { - /** - * Setter for ad data. - * @param {*} adData - */ - var ContextManager = function(adData) { - this.adData = adData - } - - ContextManager.prototype = { - CONTAINER_SIZE_TOL: 0.4, - ASPECT_RATIO_FOR_LEADERBOARDS: 2, - - /** - * Check if iframe is valid. - * @param {HTMLElement} el - * @param {HTMLElement} opt_curWin - */ - isValidContainer: function(el, opt_curWin) { - var cWidth = el.clientWidth - var cHeight = el.clientHeight - - var adWidth = this.adData.width - var adHeight = this.adData.height - - var winWidth = opt_curWin && opt_curWin.innerWidth - var winHeight = opt_curWin && opt_curWin.innerHeight - var similarWin = - opt_curWin && - this.withinTol(adWidth, winWidth) && - this.withinTol(adHeight, winHeight) - - var similarSizeX = this.withinTol(adWidth, cWidth) - var similarSizeY = this.withinTol(adHeight, cHeight) - var adAspect = adWidth / adHeight - - return ( - similarWin || - el.tagName === 'A' || - (adAspect >= this.ASPECT_RATIO_FOR_LEADERBOARDS && similarSizeY) || - (similarSizeX && similarSizeY) - ) - }, - - /** - * Check tolerance. - * @param {Int} adlen - * @param {Int} conlen - */ - withinTol: function(adlen, conlen) { - var pct = (conlen - adlen) / adlen - - return pct <= this.CONTAINER_SIZE_TOL - }, - - /** - * Serialize elements. - * @param {*} el - * @todo define parameter type. - */ - serializeElements: function(el) { - if (!el) { - return - } - var i - var ifrWin - var adId = this.adData.adId - var elIsAd = false - - if (adId && el[adId] && el[adId].isAd === true) { - elIsAd = true - } - - var json = exports.parser.elementToJSON(el, elIsAd) - var childJSON - - if (elIsAd) { - json.adId = adId - this.adData.element = {} - - var keys = Object.keys(json) - for (i = 0; i < keys.length; i++) { - var key = keys[i] - if (key !== 'children' && key !== 'contents') { - this.adData.element[key] = json[key] - } - } - } - - var children = exports.utils - .realArray(el.children) - .filter(function(el) { - var param = el.tagName === 'PARAM' - var inlineScript = - el.tagName === 'SCRIPT' && - !(el.src && el.src.indexOf('http') >= 0) - var noScript = el.tagName === 'NOSCRIPT' - return !(param || inlineScript || noScript) - }) - - for (i = 0; i < children.length; i++) { - childJSON = this.serializeElements(children[i]) - if (childJSON) { - json.children.push(childJSON) - } - } - - if (el.tagName === 'IFRAME') { - ifrWin = el.contentWindow - - if (adId && el[adId] && el[adId].needsWindow) { - json.contents = this.adData.serializedIframeContents - el[adId].needsWindow = false - delete this.adData.serializedIframeContents - } else if (exports.utils.isFriendlyWindow(ifrWin)) { - childJSON = this.serializeElements(ifrWin.document.documentElement) - if (childJSON) { - json.contents = childJSON - } - } - } - - if ( - json.children.length > 0 || - json.adId || - json.tagName === 'IFRAME' || - json.url - ) { - return json - } else { - return null - } - }, - - /** - * Get element containers. - * @param {*} containerEl - */ - captureHTML: function(containerEl) { - this.adData.context = this.serializeElements(containerEl) - }, - - /** - * Get number of Nodes. - * @param {HTMLElement} el - */ - nodeCount: function(el) { - return el.getElementsByTagName('*').length + 1 - }, - - /** - * - * @param {*} curWin - * @param {*} referenceElement - * - * @todo add description - */ - highestContainer: function(curWin, referenceElement) { - var curContainer = referenceElement - var docEl = curWin.document.documentElement - var parentContainer - - if (curWin !== curWin.top && this.isValidContainer(docEl, curWin)) { - return docEl - } - - while (true) { - parentContainer = curContainer.parentElement - if (parentContainer && this.isValidContainer(parentContainer)) { - curContainer = parentContainer - } else { - return curContainer - } - } - } - } - - var tagfinder = { - /** - * - * @param {*} adData - * @param {*} opt_el - * @param {*} opt_winPos - * - * @todo add description - */ - setPositions: function(adData, opt_el, opt_winPos) { - var el = opt_el || adData.context - var winPos = opt_winPos || { left: 0, top: 0 } - var ifrPos - - el.left += winPos.left - el.top += winPos.top - - if (el.children) { - el.children.forEach(function(child) { - this.setPositions(adData, child, winPos) - }, this) - } - - if (el.contents) { - ifrPos = { left: el.left, top: el.top } - this.setPositions(adData, el.contents, ifrPos) - } - - if (el.adId === adData.adId) { - adData.element.left = el.left - adData.element.top = el.top - } - }, - - /** - * - * @param {*} adData - * @param {*} referenceElement - * - * @todo add description - */ - appendTags: (adData, referenceElement) => { - var mgr = new ContextManager(adData) - var curWin = exports.utils.elementWindow(referenceElement) - var highestContainer - - while (true) { - highestContainer = mgr.highestContainer(curWin, referenceElement) - mgr.captureHTML(highestContainer) - if (curWin === curWin.top) { - break - } else { - curWin.mpAdFound = true - - mgr.adData.serializedIframeContents = mgr.adData.context - - if (exports.utils.isFriendlyWindow(curWin.parent)) { - referenceElement = curWin.frameElement - referenceElement[mgr.adData.adId] = { needsWindow: true } - curWin = curWin.parent - } else { - break - } - } - } - return { - referenceElement: referenceElement, - highestContainer: highestContainer - } - } - } - - exports.tagfinder = tagfinder - })(exports) - ;(function(exports) { - var _onAdFound - var _logGen = new exports.utils.LogGenerator() - var _pageTags - var INIT_MS_BW_SEARCHES = 2000 - var PAGE_TAG_RE = new RegExp('gpt|oascentral') - var POST_MSG_ID = '1554456894-8541-12665-19466-15909' - var AD_SERVER_RE = new RegExp('^(google_ads_iframe|oas_frame|atwAdFrame)') - - /** - * Get script tags from document. - * @param {Object} doc - */ - function getPageTags(doc) { - var scripts = doc.getElementsByTagName('script') - var pageTags = [] - scripts = exports.utils.realArray(scripts) - scripts.forEach(function(script) { - if (PAGE_TAG_RE.exec(script.src)) { - pageTags.push({ tagName: 'SCRIPT', url: script.src }) - } - }) - return pageTags - } - - /** - * Send message to parent iFrames. - * @param {String} adData - */ - function messageAllParentFrames(adData) { - adData.postMessageId = POST_MSG_ID - - adData = JSON.stringify(adData) - - var win = window - while (win !== win.top) { - win = win.parent - win.postMessage(adData, '*') - } - } - - /** - * - * @param {String} adData - * @param {HTMLElement} referenceElement - * - * @todo update description - */ - function appendTagsAndSendToParent(adData, referenceElement) { - var results = exports.tagfinder.appendTags(adData, referenceElement) - if (exports.utils.SCRIPT_IN_HOSTILE_IFRAME) { - messageAllParentFrames(adData) - } else if (exports.utils.SCRIPT_IN_WINDOW_TOP) { - exports.tagfinder.setPositions(adData) - - adData.matchedSize = exports.adfinder.getMatchedAdSize( - adData.width, - adData.height - ) - if (!adData.matchedSize) { - if (AD_SERVER_RE.exec(results.referenceElement.id)) { - adData.matchedSize = [adData.width, adData.height] - adData.oddSize = true - } else { - return - } - } - delete adData.width - delete adData.height - adData.curPageUrl = exports.utils.getPageUrl() - _pageTags = _pageTags || getPageTags(document) - var log = _logGen.log('ad', [adData], _pageTags) - - if (_onAdFound) { - _onAdFound(log, results.referenceElement) - } - } - } - - /** - * SetTimeout wrapper for extracting ads. - */ - function extractAdsWrapper() { - if ( - exports.utils.SCRIPT_IN_WINDOW_TOP || - document.readyState === 'complete' - ) { - extractAds() - } - setTimeout(function() { - extractAdsWrapper() - }, INIT_MS_BW_SEARCHES) - } - - /** - * Main function for extracting ads after loaded. - */ - function extractAds() { - var ads = exports.adfinder.findAds(window) - ads.forEach(function(ad) { - var startTime = new Date().getTime() - var adId = startTime + '-' + Math.floor(Math.random() * 10e12) - - var adData = { - width: Math.round(ad.offsetWidth), - height: Math.round(ad.offsetHeight), - startTime: startTime, - adId: adId, - html5: ad.html5 || false - } - - if (ad.html5) { - adData.adSizeMeta = ad.adSizeMeta || null - adData.winClickTag = ad.winClickTag || null - } - - ad[adId] = { isAd: true } - - appendTagsAndSendToParent(adData, ad) - }) - } - - /** - * Check if window is child of parent. - * @param {Object} myWin - * @param {Object} otherWin - */ - function isChildWin(myWin, otherWin) { - var parentWin = otherWin.parent - while (parentWin !== otherWin) { - if (parentWin === myWin) { - return true - } - otherWin = parentWin - parentWin = parentWin.parent - } - return false - } - - /** - * - * @param {*} win - * @param {*} winToMatch - * - * @todo update description - */ - function iframeFromWindow(win, winToMatch) { - var i, - ifr, - ifrWin, - iframes = win.document.querySelectorAll('iframe') - - for (i = 0; i < iframes.length; i++) { - ifr = iframes[i] - if (ifr.contentWindow === winToMatch) { - return ifr - } - } - - for (i = 0; i < iframes.length; i++) { - ifrWin = iframes[i].contentWindow - if (exports.utils.isFriendlyWindow(ifrWin)) { - ifr = iframeFromWindow(ifrWin, winToMatch) - if (ifr) { - return ifr - } - } - } - } - - /** - * - * @param {*} event - * - * @todo update description - */ - function onPostMessage(event) { - var adData, - ifrWin = event.source, - myWin = window.document.defaultView, - ifrTag - - if ( - typeof event.data === 'string' && - event.data.indexOf(POST_MSG_ID) != -1 - ) { - try { - adData = JSON.parse(event.data) - } catch (e) { - return - } - } else return - - if (adData.postMessageId === POST_MSG_ID) { - delete adData.postMessageId - - event.stopImmediatePropagation() - - if (isChildWin(myWin, ifrWin)) { - if (exports.utils.isFriendlyWindow(ifrWin)) { - ifrTag = ifrWin.frameElement - } else { - ifrTag = iframeFromWindow(myWin, ifrWin) - } - - if (ifrTag) { - ifrTag[adData.adId] = { needsWindow: true } - appendTagsAndSendToParent(adData, ifrTag) - } - } - } - } - - /** - * - * @param {*} msg - * @param {*} sender - * @param {*} callback - * - * @todo update description - */ - function onVideoMessage(msg, sender, callback) { - var log - if (msg.event === 'new-video-ad') { - msg.assets.forEach(function(asset) {}) - log = _logGen.log('video', msg.assets) - } else { - log = _logGen.log('invalid-video', msg.assets) - } - - msg.assets.forEach(function(a) { - delete a.isVideo - }) - log.displayAdFound = msg.displayAdFound - log.requests = msg.requests - log.data = msg.event_data - - log.doc.finalPageUrl = log.doc.url - log.doc.url = exports.utils.normalizeUrl(msg.origUrl) - - _onAdFound(log) - } - - /** - * Add background listener. - * @param {String} event - * @param {Function} callback - */ - function addBackgroundListener(event, callback) { - chrome.runtime.onMessage.addListener(function(msg) { + }, + + /** + * Check if anonymous tracking is enabled. + * @param {Function} callback + * @param {Function} elseCallback + * @todo validate if utilCallback or utilElseCallback are being used. + */ + askIfTrackingEnabled(callback, elseCallback) { + utilCallback = callback + utilElseCallback = elseCallback + + this.sendToBackground( + 'is_tracking_enabled', + '', + 'tracking_enabled_response' + ) + }, + } + + utils.SCRIPT_IN_FRIENDLY_IFRAME = + !utils.SCRIPT_IN_WINDOW_TOP && utils.isFriendlyWindow(window.parent) + utils.SCRIPT_IN_HOSTILE_IFRAME = + !utils.SCRIPT_IN_WINDOW_TOP && !utils.SCRIPT_IN_FRIENDLY_IFRAME + + /** + * Generate new Logging object. + */ + function LogGenerator() { + this.msgNum = 0 + this.pageMeta = { + url: utils.getPageUrl(), + isHP: window.location.pathname === '/', + referrer: utils.getReferrer(), + rand: Math.floor(Math.random() * 10e12), + startTime: new Date().getTime(), + } + } + + LogGenerator.prototype = { + /** + * Log data. + * @param {String} event + * @param {Array} opt_assets + * @param {Array} opt_pageTags + */ + log(event, opt_assets, opt_pageTags) { + let opt_video_assets + if (event === 'video' || event === 'invalid-video') { + opt_video_assets = opt_assets || [] + opt_assets = [] + } else { + opt_video_assets = [] + opt_assets = opt_assets || [] + } + const result = { + doc: this.pageMeta, + event, + video_assets: opt_video_assets, + assets: opt_assets, + version: '3', + mrev: '15a9f21-d', + msgNum: this.msgNum, + timestamp: new Date().getTime(), + pageVis: document.visibilityState, + pageFoc: document.hasFocus(), + pageTags: opt_pageTags || [], + } + this.msgNum++ + return result + }, + } + + utils.LogGenerator = LogGenerator + + let utilCallback, utilElseCallback + + exports.utils = utils + })(exports) + ;(function (exports) { + const SizeMatcher = { + VALID_AD_SIZES: [ + [300, 50], + [320, 50], + [160, 600], + [300, 250], + [300, 600], + [300, 1050], + [336, 280], + [336, 850], + [468, 60], + [728, 90], + [728, 250], + [728, 270], + [970, 66], + [970, 90], + [970, 125], + [970, 250], + [970, 400], + [970, 415], + [1280, 100], + ], + + PX_SIZE_TOL: 10, + + /** + * Get ad size. + * @param {Int} width + * @param {Int} height + */ + getMatchedAdSize(width, height) { + if (!this.set) { + this.set = this._makeSizeSet() + } + + return this.set[Math.round(width) + 'x' + Math.round(height)] + }, + + /** + * Check element size. + * @param {HTMLElement} el + */ + elementIsAdShaped(el) { + return !!this.getMatchedAdSizeForElement(el) + }, + + /** + * Get ad size. + * @param {HTMLElement} el + * @todo better description + */ + getMatchedAdSizeForElement(el) { + const rect = el.getBoundingClientRect() + return this.getMatchedAdSize(rect.width, rect.height) + }, + + /** + * Generate ad sizes. + */ + _makeSizeSet() { + const set = {} + let i + let xfuz + let yfuz + let size + let width + let height + + for (i = 0; i < this.VALID_AD_SIZES.length; i++) { + for (xfuz = -this.PX_SIZE_TOL; xfuz <= this.PX_SIZE_TOL; xfuz++) { + for (yfuz = -this.PX_SIZE_TOL; yfuz <= this.PX_SIZE_TOL; yfuz++) { + size = this.VALID_AD_SIZES[i] + width = size[0] + xfuz + height = size[1] + yfuz + set[width + 'x' + height] = size + } + } + } + return set + }, + } + + const Throttler = { + MAX_SEARCHES_PER_WINDOW: 10, + MAX_SEARCHES_PER_ELEMENT: 2, + + /** + * Count number of elements. + * @param {HTMLElement} el + */ + countSearch: (el) => { + if (typeof el.searches !== 'number') { + el.searches = 0 + } + + el.searches += 1 + }, + + /** + * + * @param {*} el + * @param {*} max + * + * @todo add description + */ + throttle(el, max) { + if (typeof el.searches === 'number' && el.searches >= max) { + return true + } + return false + }, + + /** + * + * @param {*} el + * + * @todo add description + */ + throttleElement(el) { + return this.throttle(el, this.MAX_SEARCHES_PER_ELEMENT) + }, + + /** + * + * @param {*} win + * + * @todo add description + */ + throttleWin(win) { + return this.throttle(win, this.MAX_SEARCHES_PER_WINDOW) + }, + + /** + * + * @param {*} el + * + * @todo add description + */ + getCount(el) { + return el.searches || 0 + }, + } + + /** + * Initialize window and document elements. + * @param {*} win + */ + function TopSearcher(win) { + this.win = win + this.doc = win.document + } + + /** + * Add search function. + */ + TopSearcher.prototype.search = function () { + const candidates = exports.utils.realArray( + this.doc.querySelectorAll('img, object, embed') + ) + let html5Ad + let ads = [] + + ads = ads.concat( + candidates.filter(function (el) { + if (!el.mpAdFound && !Throttler.throttleElement(el)) { + Throttler.countSearch(el) + if ( + (el.tagName !== 'IMG' || isStandardImage(el)) && + SizeMatcher.elementIsAdShaped(el) + ) { + el.mpAdFound = true + return true + } + } + return false + }) + ) + + html5Ad = this._mainGetHTMLAd() + if (html5Ad) { + html5Ad.html5 = true + html5Ad.mpAdFound = true + ads.push(html5Ad) + } + + return ads + } + + /** + * @todo add description + */ + TopSearcher.prototype._mainGetHTMLAd = function () { + const styles = this.doc.querySelectorAll( + 'div > style, div > link[rel="stylesheet"]' + ) + let i + let div + for (i = 0; i < styles.length; i++) { + div = styles[i].parentNode + if ( + !div.mpAdFound && + SizeMatcher.elementIsAdShaped(div) && + this._jumpedOut(div) + ) { + return div + } + } + } + + /** + * @todo add description + */ + TopSearcher.prototype._jumpedOut = function (el) { + let siblings, ifrs + siblings = exports.utils.realArray(el.parentNode.children) + ifrs = siblings.filter(function (el) { + return ( + el.tagName === 'IFRAME' && + el.offsetWidth === 0 && + el.offsetHeight === 0 + ) + }) + return ifrs.length > 0 + } + + /** + * + * @param {*} win + * + * @todo add description + */ + function IframeSearcher(win) { + this.MIN_AD_AREA = 14000 + this.MIN_WINDOW_PX = 10 + + this.win = win + this.doc = win.document + this.body = win.document.body + this.winClickTag = win.clickTag + this.adSizeMeta = this._getAdSizeMeta() + this.numElementsInBody = + (this.body && this.body.querySelectorAll('*').length) || 0 + + this.shouldSearchWindow = false + if ( + !this.win.mpAdFound && + this.body && + !Throttler.throttleWin(this.win) + ) { + this.winWidth = this.win.innerWidth + this.winHeight = this.win.innerHeight + if ( + this._meetsMinAdSize(this.winWidth, this.winHeight) && + !this._containsLargeIframes() + ) { + this.shouldSearchWindow = true + } + } + } + + /** + * @todo add description + */ + IframeSearcher.prototype.search = function () { + let ad + + if (this.shouldSearchWindow) { + ad = this._search() + if (ad) { + ad.mpAdFound = true + win.mpAdFound = true + return ad + } + Throttler.countSearch(this.win) + } + + return null + } + + /** + * @todo add description + */ + IframeSearcher.prototype._search = function () { + const _this = this + let stdCandidates + let html5Candidates + let stdEl + let html5El + + stdCandidates = this.body.querySelectorAll('img, object, embed') + + stdEl = getFirst(stdCandidates, function (el) { + if ( + !el.mpAdFound && + !Throttler.throttleElement(el) && + (el.tagName !== 'IMG' || isStandardImage(el)) && + _this._elementIsAtLeastAsBigAsWindow(el) + ) { + return true + } + Throttler.countSearch(el) + return false + }) + + if (stdEl) { + return stdEl + } + + if (this._isHTML5Iframe()) { + html5Candidates = this.doc.querySelectorAll( + 'body, canvas, button, video, svg, div' + ) + html5El = getFirst(html5Candidates, function (el) { + if (_this._elementIsAtLeastAsBigAsWindow(el)) { + return true + } + Throttler.countSearch(el) + return false + }) + } + + if (html5El) { + html5El.html5 = true + html5El.winClickTag = this.winClickTag + html5El.adSizeMeta = this.adSizeMeta + return html5El + } + + return null + } + + /** + * @todo add description + */ + IframeSearcher.prototype._isHTML5Iframe = function () { + if (this.winClickTag || this.adSizeMeta) { + return true + } + + if ( + this.doc.querySelectorAll('canvas', 'button', 'video', 'svg').length > 0 + ) { + return true + } + + if ( + this.numElementsInBody >= 5 && + Throttler.getCount(this.win) > 0 && + this.doc.querySelectorAll('div').length > 0 + ) { + return true + } + + return false + } + + /** + * @todo add description + */ + IframeSearcher.prototype._elementIsAtLeastAsBigAsWindow = function (el) { + const rect = el.getBoundingClientRect() + const tol = 0.95 + + return ( + rect.width >= tol * this.winWidth && rect.height >= tol * this.winHeight + ) + } + + /** + * @todo add description + */ + IframeSearcher.prototype._meetsMinAdSize = function (width, height) { + return width * height >= this.MIN_AD_AREA + } + + /** + * @todo add description + */ + IframeSearcher.prototype._containsLargeIframes = function () { + const iframes = this.doc.querySelectorAll('iframe') + let rect + let i + for (i = 0; i < iframes.length; i++) { + rect = iframes[i].getBoundingClientRect() + if ( + rect.width > this.MIN_WINDOW_PX || + rect.height > this.MIN_WINDOW_PX + ) { + return true + } + } + return false + } + + /** + * @todo add description + */ + IframeSearcher.prototype._getAdSizeMeta = function () { + const adSizeMeta = this.doc.querySelectorAll('meta[name="ad.size"]') + if (adSizeMeta.length > 0) { + return adSizeMeta[0].content + } else { + return null + } + } + + /** + * + * @param {*} arr + * @param {*} testFn + * + * @todo add description + */ + function getFirst(arr, testFn) { + let i, el + for (i = 0; i < arr.length; i++) { + el = arr[i] + if (testFn(el)) { + return el + } + } + return null + } + + /** + * Check for image attributes. + * @param {HTMLElement} img + */ + function isStandardImage(img) { + return ( + img.src && + (img.parentNode.tagName === 'A' || img.getAttribute('onclick')) + ) + } + + /** + * Extract iFrames from page. + * @param {Object} win + */ + function getFriendlyIframes(win) { + let iframes = win.document.querySelectorAll('iframe') + iframes = exports.utils.realArray(iframes) + const friendlyIframes = iframes.filter(function (ifr) { + return exports.utils.isFriendlyWindow(ifr.contentWindow) + }) + return friendlyIframes + } + + /** + * + * @param {*} win + */ + function findAds(win) { + let i + let iframes + let searcher + let ad + let ads = [] + + if (win === win.top) { + searcher = new TopSearcher(win) + ads = ads.concat(searcher.search()) + } else { + searcher = new IframeSearcher(win) + ad = searcher.search() + if (ad) { + ads.push(ad) + } + } + + iframes = getFriendlyIframes(win) + for (i = 0; i < iframes.length; i++) { + ads = ads.concat(findAds(iframes[i].contentWindow)) + } + + return ads + } + + exports.adfinder = { + getMatchedAdSize: SizeMatcher.getMatchedAdSize.bind(SizeMatcher), + findAds, + } + })(exports) + ;(function (exports) { + const parser = { + TAGS_WITH_SRC_ATTR: { + IMG: true, + SCRIPT: true, + IFRAME: true, + EMBED: true, + }, + + MAX_ATTR_LEN: 100, + + /** + * + * @param {*} el + * @param {*} params + * + * @todo add description + */ + getUrl(el, params) { + let url + + if (this.TAGS_WITH_SRC_ATTR.hasOwnProperty(el.tagName)) { + url = el.src + } else if (el.tagName === 'OBJECT') { + url = el.data || (params && params.movie) || null + } else if (el.tagName === 'A') { + url = el.href + } + + if (url && url.indexOf('http') === 0) { + return url + } else { + return null + } + }, + + /** + * + * @param {*} el + * + * @todo add description + */ + getParams(el) { + if (el.tagName !== 'OBJECT') { + return null + } + + let i, child + const params = {} + const children = el.children + for (i = 0; i < children.length; i++) { + child = children[i] + if (child.tagName === 'PARAM' && child.name) { + params[child.name.toLowerCase()] = child.value + } + } + return params + }, + + /** + * Get element position. + * @param {HTMLElement} el + */ + getPosition(el) { + const rect = el.getBoundingClientRect() + const win = exports.utils.elementWindow(el) + + return { + width: Math.round(rect.width), + height: Math.round(rect.height), + left: Math.round(rect.left + win.pageXOffset), + top: Math.round(rect.top + win.pageYOffset), + } + }, + + /** + * + * @param {*} el + * @param {*} params + * @param {*} url + * + * @todo add description + */ + getFlashvars(el, params, url) { + let flashvars + const urlQS = url && url.split('?')[1] + + if (el.tagName === 'EMBED') { + flashvars = el.getAttribute('flashvars') || urlQS + } else if (el.tagName === 'OBJECT') { + flashvars = params.flashvars || el.getAttribute('flashvars') || urlQS + } + + return (flashvars && exports.utils.parseQS(flashvars)) || null + }, + + /** + * + * @param {*} el + * @param {*} flashvars + * + * @todo add description + */ + findClickThru(el, flashvars) { + let key + if (el.tagName === 'IMG' && el.parentElement.tagName === 'A') { + return el.parentElement.href + } else if (flashvars) { + for (key in flashvars) { + if (flashvars.hasOwnProperty(key)) { + if (key.toLowerCase().indexOf('clicktag') === 0) { + return flashvars[key] + } + } + } + } + return null + }, + + /** + * Get element attribute. + * @param {HTMLElement} el + * @param {String} name + */ + getAttr(el, name) { + const val = el.getAttribute(name) + + if (val && val.slice && val.toString) { + return val.slice(0, this.MAX_ATTR_LEN).toString() + } else { + return null + } + }, + + /** + * + * @param {*} obj + * @param {*} name + * @param {*} val + * + * @todo add description + */ + putPropIfExists(obj, name, val) { + if (val) { + obj[name] = val + } + }, + + /** + * + * @param {*} obj + * @param {*} el + * @param {*} name + * + * @todo add description + */ + putAttrIfExists(obj, el, name) { + const val = this.getAttr(el, name) + this.putPropIfExists(obj, name, val) + }, + + /** + * Convert Element to JSON + * @param {HTMLElement} el + * @param {Boolean} opt_findClickThru + */ + elementToJSON(el, opt_findClickThru) { + const pos = this.getPosition(el) + const params = this.getParams(el) + const url = this.getUrl(el, params) + const flashvars = this.getFlashvars(el, params, url) + const clickThru = opt_findClickThru && this.findClickThru(el, flashvars) + const json = { + tagName: el.tagName, + width: pos.width, + height: pos.height, + left: pos.left, + top: pos.top, + children: [], + } + + if (params) { + delete params.flashvars + } + + this.putAttrIfExists(json, el, 'id') + this.putAttrIfExists(json, el, 'class') + this.putAttrIfExists(json, el, 'name') + + this.putPropIfExists(json, 'flashvars', flashvars) + this.putPropIfExists(json, 'url', url) + this.putPropIfExists(json, 'params', params) + this.putPropIfExists(json, 'clickThru', clickThru) + + return json + }, + } + + exports.parser = { elementToJSON: parser.elementToJSON.bind(parser) } + })(exports) + + // Anonymous invocation. + ;(function (exports) { + /** + * Setter for ad data. + * @param {*} adData + */ + const ContextManager = function (adData) { + this.adData = adData + } + + ContextManager.prototype = { + CONTAINER_SIZE_TOL: 0.4, + ASPECT_RATIO_FOR_LEADERBOARDS: 2, + + /** + * Check if iframe is valid. + * @param {HTMLElement} el + * @param {HTMLElement} opt_curWin + */ + isValidContainer(el, opt_curWin) { + const cWidth = el.clientWidth + const cHeight = el.clientHeight + + const adWidth = this.adData.width + const adHeight = this.adData.height + + const winWidth = opt_curWin && opt_curWin.innerWidth + const winHeight = opt_curWin && opt_curWin.innerHeight + const similarWin = + opt_curWin && + this.withinTol(adWidth, winWidth) && + this.withinTol(adHeight, winHeight) + + const similarSizeX = this.withinTol(adWidth, cWidth) + const similarSizeY = this.withinTol(adHeight, cHeight) + const adAspect = adWidth / adHeight + + return ( + similarWin || + el.tagName === 'A' || + (adAspect >= this.ASPECT_RATIO_FOR_LEADERBOARDS && similarSizeY) || + (similarSizeX && similarSizeY) + ) + }, + + /** + * Check tolerance. + * @param {Int} adlen + * @param {Int} conlen + */ + withinTol(adlen, conlen) { + const pct = (conlen - adlen) / adlen + + return pct <= this.CONTAINER_SIZE_TOL + }, + + /** + * Serialize elements. + * @param {*} el + * @todo define parameter type. + */ + serializeElements(el) { + if (!el) { + return + } + let i + let ifrWin + const adId = this.adData.adId + let elIsAd = false + + if (adId && el[adId] && el[adId].isAd === true) { + elIsAd = true + } + + const json = exports.parser.elementToJSON(el, elIsAd) + let childJSON + + if (elIsAd) { + json.adId = adId + this.adData.element = {} + + const keys = Object.keys(json) + for (i = 0; i < keys.length; i++) { + const key = keys[i] + if (key !== 'children' && key !== 'contents') { + this.adData.element[key] = json[key] + } + } + } + + const children = exports.utils + .realArray(el.children) + .filter(function (el) { + const param = el.tagName === 'PARAM' + const inlineScript = + el.tagName === 'SCRIPT' && !(el.src && el.src.includes('http')) + const noScript = el.tagName === 'NOSCRIPT' + return !(param || inlineScript || noScript) + }) + + for (i = 0; i < children.length; i++) { + childJSON = this.serializeElements(children[i]) + if (childJSON) { + json.children.push(childJSON) + } + } + + if (el.tagName === 'IFRAME') { + ifrWin = el.contentWindow + + if (adId && el[adId] && el[adId].needsWindow) { + json.contents = this.adData.serializedIframeContents + el[adId].needsWindow = false + delete this.adData.serializedIframeContents + } else if (exports.utils.isFriendlyWindow(ifrWin)) { + childJSON = this.serializeElements(ifrWin.document.documentElement) + if (childJSON) { + json.contents = childJSON + } + } + } + + if ( + json.children.length > 0 || + json.adId || + json.tagName === 'IFRAME' || + json.url + ) { + return json + } else { + return null + } + }, + + /** + * Get element containers. + * @param {*} containerEl + */ + captureHTML(containerEl) { + this.adData.context = this.serializeElements(containerEl) + }, + + /** + * Get number of Nodes. + * @param {HTMLElement} el + */ + nodeCount(el) { + return el.getElementsByTagName('*').length + 1 + }, + + /** + * + * @param {*} curWin + * @param {*} referenceElement + * + * @todo add description + */ + highestContainer(curWin, referenceElement) { + let curContainer = referenceElement + const docEl = curWin.document.documentElement + let parentContainer + + if (curWin !== curWin.top && this.isValidContainer(docEl, curWin)) { + return docEl + } + + while (true) { + parentContainer = curContainer.parentElement + if (parentContainer && this.isValidContainer(parentContainer)) { + curContainer = parentContainer + } else { + return curContainer + } + } + }, + } + + const tagfinder = { + /** + * + * @param {*} adData + * @param {*} opt_el + * @param {*} opt_winPos + * + * @todo add description + */ + setPositions(adData, opt_el, opt_winPos) { + const el = opt_el || adData.context + const winPos = opt_winPos || { left: 0, top: 0 } + let ifrPos + + el.left += winPos.left + el.top += winPos.top + + if (el.children) { + el.children.forEach(function (child) { + this.setPositions(adData, child, winPos) + }, this) + } + + if (el.contents) { + ifrPos = { left: el.left, top: el.top } + this.setPositions(adData, el.contents, ifrPos) + } + + if (el.adId === adData.adId) { + adData.element.left = el.left + adData.element.top = el.top + } + }, + + /** + * + * @param {*} adData + * @param {*} referenceElement + * + * @todo add description + */ + appendTags: (adData, referenceElement) => { + const mgr = new ContextManager(adData) + let curWin = exports.utils.elementWindow(referenceElement) + let highestContainer + + while (true) { + highestContainer = mgr.highestContainer(curWin, referenceElement) + mgr.captureHTML(highestContainer) + if (curWin === curWin.top) { + break + } else { + curWin.mpAdFound = true + + mgr.adData.serializedIframeContents = mgr.adData.context + + if (exports.utils.isFriendlyWindow(curWin.parent)) { + referenceElement = curWin.frameElement + referenceElement[mgr.adData.adId] = { needsWindow: true } + curWin = curWin.parent + } else { + break + } + } + } + return { + referenceElement, + highestContainer, + } + }, + } + + exports.tagfinder = tagfinder + })(exports) + ;(function (exports) { + let _onAdFound + const _logGen = new exports.utils.LogGenerator() + let _pageTags + const INIT_MS_BW_SEARCHES = 2000 + const PAGE_TAG_RE = new RegExp('gpt|oascentral') + const POST_MSG_ID = '1554456894-8541-12665-19466-15909' + const AD_SERVER_RE = new RegExp('^(google_ads_iframe|oas_frame|atwAdFrame)') + + /** + * Get script tags from document. + * @param {Object} doc + */ + function getPageTags(doc) { + let scripts = doc.getElementsByTagName('script') + const pageTags = [] + scripts = exports.utils.realArray(scripts) + scripts.forEach(function (script) { + if (PAGE_TAG_RE.exec(script.src)) { + pageTags.push({ tagName: 'SCRIPT', url: script.src }) + } + }) + return pageTags + } + + /** + * Send message to parent iFrames. + * @param {String} adData + */ + function messageAllParentFrames(adData) { + adData.postMessageId = POST_MSG_ID + + adData = JSON.stringify(adData) + + let win = window + while (win !== win.top) { + win = win.parent + win.postMessage(adData, '*') + } + } + + /** + * + * @param {String} adData + * @param {HTMLElement} referenceElement + * + * @todo update description + */ + function appendTagsAndSendToParent(adData, referenceElement) { + const results = exports.tagfinder.appendTags(adData, referenceElement) + if (exports.utils.SCRIPT_IN_HOSTILE_IFRAME) { + messageAllParentFrames(adData) + } else if (exports.utils.SCRIPT_IN_WINDOW_TOP) { + exports.tagfinder.setPositions(adData) + + adData.matchedSize = exports.adfinder.getMatchedAdSize( + adData.width, + adData.height + ) + if (!adData.matchedSize) { + if (AD_SERVER_RE.exec(results.referenceElement.id)) { + adData.matchedSize = [adData.width, adData.height] + adData.oddSize = true + } else { + return + } + } + delete adData.width + delete adData.height + adData.curPageUrl = exports.utils.getPageUrl() + _pageTags = _pageTags || getPageTags(document) + const log = _logGen.log('ad', [adData], _pageTags) + + if (_onAdFound) { + _onAdFound(log, results.referenceElement) + } + } + } + + /** + * SetTimeout wrapper for extracting ads. + */ + function extractAdsWrapper() { + if ( + exports.utils.SCRIPT_IN_WINDOW_TOP || + document.readyState === 'complete' + ) { + extractAds() + } + setTimeout(function () { + extractAdsWrapper() + }, INIT_MS_BW_SEARCHES) + } + + /** + * Main function for extracting ads after loaded. + */ + function extractAds() { + const ads = exports.adfinder.findAds(window) + ads.forEach(function (ad) { + const startTime = new Date().getTime() + const adId = startTime + '-' + Math.floor(Math.random() * 10e12) + + const adData = { + width: Math.round(ad.offsetWidth), + height: Math.round(ad.offsetHeight), + startTime, + adId, + html5: ad.html5 || false, + } + + if (ad.html5) { + adData.adSizeMeta = ad.adSizeMeta || null + adData.winClickTag = ad.winClickTag || null + } + + ad[adId] = { isAd: true } + + appendTagsAndSendToParent(adData, ad) + }) + } + + /** + * Check if window is child of parent. + * @param {Object} myWin + * @param {Object} otherWin + */ + function isChildWin(myWin, otherWin) { + let parentWin = otherWin.parent + while (parentWin !== otherWin) { + if (parentWin === myWin) { + return true + } + otherWin = parentWin + parentWin = parentWin.parent + } + return false + } + + /** + * + * @param {*} win + * @param {*} winToMatch + * + * @todo update description + */ + function iframeFromWindow(win, winToMatch) { + let i + let ifr + let ifrWin + const iframes = win.document.querySelectorAll('iframe') + + for (i = 0; i < iframes.length; i++) { + ifr = iframes[i] + if (ifr.contentWindow === winToMatch) { + return ifr + } + } + + for (i = 0; i < iframes.length; i++) { + ifrWin = iframes[i].contentWindow + if (exports.utils.isFriendlyWindow(ifrWin)) { + ifr = iframeFromWindow(ifrWin, winToMatch) + if (ifr) { + return ifr + } + } + } + } + + /** + * + * @param {*} event + * + * @todo update description + */ + function onPostMessage(event) { + let adData + const ifrWin = event.source + const myWin = window.document.defaultView + let ifrTag + + if (typeof event.data === 'string' && event.data.includes(POST_MSG_ID)) { + try { + adData = JSON.parse(event.data) + } catch (e) { + return + } + } else return + + if (adData.postMessageId === POST_MSG_ID) { + delete adData.postMessageId + + event.stopImmediatePropagation() + + if (isChildWin(myWin, ifrWin)) { + if (exports.utils.isFriendlyWindow(ifrWin)) { + ifrTag = ifrWin.frameElement + } else { + ifrTag = iframeFromWindow(myWin, ifrWin) + } + + if (ifrTag) { + ifrTag[adData.adId] = { needsWindow: true } + appendTagsAndSendToParent(adData, ifrTag) + } + } + } + } + + /** + * + * @param {*} msg + * @param {*} sender + * @param {*} callback + * + * @todo update description + */ + function onVideoMessage(msg, sender, callback) { + let log + if (msg.event === 'new-video-ad') { + msg.assets.forEach(function (asset) {}) + log = _logGen.log('video', msg.assets) + } else { + log = _logGen.log('invalid-video', msg.assets) + } + + msg.assets.forEach(function (a) { + delete a.isVideo + }) + log.displayAdFound = msg.displayAdFound + log.requests = msg.requests + log.data = msg.event_data + + log.doc.finalPageUrl = log.doc.url + log.doc.url = exports.utils.normalizeUrl(msg.origUrl) + + _onAdFound(log) + } + + /** + * Add background listener. + * @param {String} event + * @param {Function} callback + */ + function addBackgroundListener(event, callback) { + chrome.runtime.onMessage.addListener(function (msg) { if (msg.event === event) { callback(msg) } }) - } - - exports.coordinator = { - /** - * @todo update description - */ - addPostMessageListener: function() { - if (!exports.utils.SCRIPT_IN_FRIENDLY_IFRAME) { - window.addEventListener('message', onPostMessage, false) - } - }, - - /** - * - * @param {*} sendFcn - * @param {*} origUrl - * - * @todo update description - */ - blockedRobotsMsgGen: function(sendFcn, origUrl) { - if (origUrl.indexOf('google.com/_/chrome/newtab') === -1) { - var onBlockedRobotsMessage = function() { - var log - log = _logGen.log('invalid-robotstxt', []) - log.doc.finalPageUrl = log.doc.url - log.doc.url = exports.utils.normalizeUrl(origUrl) - - sendFcn(log) - } - return onBlockedRobotsMessage - } else { - return function() {} - } - }, - - /** - * - * @param {*} onAdFound - */ - init: function(onAdFound) { - if (exports.utils.SCRIPT_IN_FRIENDLY_IFRAME) { - return false - } - - _onAdFound = onAdFound - if (exports.utils.SCRIPT_IN_WINDOW_TOP) { - var log = _logGen.log('page') - onAdFound(log) - - window.addEventListener('beforeunload', function(event) { - var log = _logGen.log('unload') - log.timing = window.performance.timing - onAdFound(log) - }) - - addBackgroundListener('new-video-ad', onVideoMessage) - addBackgroundListener('new-invalid-video-ad', onVideoMessage) - } - - exports.utils.onDocLoaded(document, extractAdsWrapper) - } - } - })(exports) - - if (exports.utils.SCRIPT_IN_WINDOW_TOP) { - window.adparser = { - init: exports.coordinator.init, - addPostMessageListener: exports.coordinator.addPostMessageListener, - askIfTrackingEnabled: exports.utils.askIfTrackingEnabled, - blockedRobotsMsgGen: exports.coordinator.blockedRobotsMsgGen, - inWindowTop: exports.utils.SCRIPT_IN_WINDOW_TOP, - sendToBackground: exports.utils.sendToBackground - } - } else { - exports.coordinator.addPostMessageListener() - exports.utils.askIfTrackingEnabled( - function() { - exports.coordinator.init(function() {}) - }, - function() {} - ) - } + } + + exports.coordinator = { + /** + * @todo update description + */ + addPostMessageListener() { + if (!exports.utils.SCRIPT_IN_FRIENDLY_IFRAME) { + window.addEventListener('message', onPostMessage, false) + } + }, + + /** + * + * @param {*} sendFcn + * @param {*} origUrl + * + * @todo update description + */ + blockedRobotsMsgGen(sendFcn, origUrl) { + if (!origUrl.includes('google.com/_/chrome/newtab')) { + const onBlockedRobotsMessage = function () { + let log + log = _logGen.log('invalid-robotstxt', []) + log.doc.finalPageUrl = log.doc.url + log.doc.url = exports.utils.normalizeUrl(origUrl) + + sendFcn(log) + } + return onBlockedRobotsMessage + } else { + return function () {} + } + }, + + /** + * + * @param {*} onAdFound + */ + init(onAdFound) { + if (exports.utils.SCRIPT_IN_FRIENDLY_IFRAME) { + return false + } + + _onAdFound = onAdFound + if (exports.utils.SCRIPT_IN_WINDOW_TOP) { + const log = _logGen.log('page') + onAdFound(log) + + window.addEventListener('beforeunload', function (event) { + const log = _logGen.log('unload') + log.timing = window.performance.timing + onAdFound(log) + }) + + addBackgroundListener('new-video-ad', onVideoMessage) + addBackgroundListener('new-invalid-video-ad', onVideoMessage) + } + + exports.utils.onDocLoaded(document, extractAdsWrapper) + }, + } + })(exports) + + if (exports.utils.SCRIPT_IN_WINDOW_TOP) { + window.adparser = { + init: exports.coordinator.init, + addPostMessageListener: exports.coordinator.addPostMessageListener, + askIfTrackingEnabled: exports.utils.askIfTrackingEnabled, + blockedRobotsMsgGen: exports.coordinator.blockedRobotsMsgGen, + inWindowTop: exports.utils.SCRIPT_IN_WINDOW_TOP, + sendToBackground: exports.utils.sendToBackground, + } + } else { + exports.coordinator.addPostMessageListener() + exports.utils.askIfTrackingEnabled( + function () { + exports.coordinator.init(function () {}) + }, + function () {} + ) + } })(window) -;(function(adparser, pageUrl) { - function onAdFound(log) { - adparser.sendToBackground( - { source: 'iframe.js', func: 'onAd', args: [log] }, - 'onAd', - '', - function() {} - ) - } - - if (adparser && adparser.inWindowTop) { - adparser.addPostMessageListener() - adparser.askIfTrackingEnabled(function() { - adparser.init(onAdFound) - }, adparser.blockedRobotsMsgGen(onAdFound, pageUrl)) - } +;(function (adparser, pageUrl) { + function onAdFound(log) { + adparser.sendToBackground( + { source: 'iframe.js', func: 'onAd', args: [log] }, + 'onAd', + '', + function () {} + ) + } + + if (adparser && adparser.inWindowTop) { + adparser.addPostMessageListener() + adparser.askIfTrackingEnabled(function () { + adparser.init(onAdFound) + }, adparser.blockedRobotsMsgGen(onAdFound, pageUrl)) + } })(window.adparser, window.location.href) diff --git a/src/drivers/webextension/js/lib/network.js b/src/drivers/webextension/js/lib/network.js index 3e0c8d46a..7b0573205 100644 --- a/src/drivers/webextension/js/lib/network.js +++ b/src/drivers/webextension/js/lib/network.js @@ -1,747 +1,860 @@ -'use strict'; - -(function() { - var MIN_FF_MAJOR_VERSION = 51; - - var areListenersRegistered = false; - var secBefore = 2000; - var secAfter = 5000; - var secBetweenDupAssets = 10e3; - var minVidSize = 500e3; - var maxVidSize = 25e6; - var maxContentRange = 25e6; - var videoExtensions = [ - 'af', '3gp', 'asf', 'avchd', 'avi', 'cam', 'dsh', 'flv', 'm1v', 'm2v', - 'fla', 'flr', 'sol', 'm4v', 'mkv', 'wrap', 'mng', 'mov', 'mpeg', 'mpg', - 'mpe', 'mp4', 'mxf', 'nsv', 'ogg', 'rm', 'svi', 'smi', 'wmv', 'webm' - ]; - var extensionsReg = new RegExp('\\.' + videoExtensions.join('$|\\.') + '$'); - var videoContentTypesPrefixes = ['binary/octet-stream', 'video/', 'flv-application/', 'media']; - - var bannedContentTypes = ['video/mp2t','video/f4m','video/f4f']; - var bannedFiletypes = ['ts']; - var bannedFiletypesReg = new RegExp('\\.' + bannedFiletypes.join('$|\\.') + '$'); - var whitelistReqTypes = ['object', 'xmlhttprequest', 'other']; - - var topVideoAssetDomains = [ - '2mdn.net', - 'adap.tv', - 'adnxs.com', - 'adsrvr.org', - 'btrll.com', - 'celtra.com', - 'flashtalking.com', - 'flite.com', - 'innovid.com', - 'jivox.com', - 'mixpo.com', - 'nytimes.com', - 'playwire.com', - 'selectmedia.asia', - 'serving-sys.com', - 'solvemedia.com', - 'spotible.com', - 'teads.tv', - 'tribalfusion.com', - 'tubemogul.com', - 'videologygroup.com', - 'washingtonpost.com' - ]; - - var robotsTxtAllows = Driver.checkRobots; - if ( !String.prototype.endsWith ) { - String.prototype.endsWith = function(searchString, position) { - var subjectString = this.toString(); - if ( typeof position !== 'number' || !isFinite(position) || - Math.floor(position) !== position || position > subjectString.length) { - position = subjectString.length; - } - position -= searchString.length; - var lastIndex = subjectString.indexOf(searchString, position); - return lastIndex !== -1 && lastIndex === position; - }; - } - - function getFrame(getFrameDetails, callback) { - chrome.webNavigation.getFrame(getFrameDetails, callback); - } - - function ifTrackingEnabled(details, ifCallback, elseCallback) { - - var fullIfCallback = function() { - allowedByRobotsTxt(details, ifCallback, elseCallback); - }; - - Utils.getOption('tracking', true).then(function(tracking) { - if ( tracking ) { - fullIfCallback(); +'use strict' +;(function () { + const MIN_FF_MAJOR_VERSION = 51 + + let areListenersRegistered = false + const secBefore = 2000 + const secAfter = 5000 + const secBetweenDupAssets = 10e3 + const minVidSize = 500e3 + const maxVidSize = 25e6 + const maxContentRange = 25e6 + const videoExtensions = [ + 'af', + '3gp', + 'asf', + 'avchd', + 'avi', + 'cam', + 'dsh', + 'flv', + 'm1v', + 'm2v', + 'fla', + 'flr', + 'sol', + 'm4v', + 'mkv', + 'wrap', + 'mng', + 'mov', + 'mpeg', + 'mpg', + 'mpe', + 'mp4', + 'mxf', + 'nsv', + 'ogg', + 'rm', + 'svi', + 'smi', + 'wmv', + 'webm', + ] + const extensionsReg = new RegExp('\\.' + videoExtensions.join('$|\\.') + '$') + const videoContentTypesPrefixes = [ + 'binary/octet-stream', + 'video/', + 'flv-application/', + 'media', + ] + + const bannedContentTypes = ['video/mp2t', 'video/f4m', 'video/f4f'] + const bannedFiletypes = ['ts'] + const bannedFiletypesReg = new RegExp( + '\\.' + bannedFiletypes.join('$|\\.') + '$' + ) + const whitelistReqTypes = ['object', 'xmlhttprequest', 'other'] + + const topVideoAssetDomains = [ + '2mdn.net', + 'adap.tv', + 'adnxs.com', + 'adsrvr.org', + 'btrll.com', + 'celtra.com', + 'flashtalking.com', + 'flite.com', + 'innovid.com', + 'jivox.com', + 'mixpo.com', + 'nytimes.com', + 'playwire.com', + 'selectmedia.asia', + 'serving-sys.com', + 'solvemedia.com', + 'spotible.com', + 'teads.tv', + 'tribalfusion.com', + 'tubemogul.com', + 'videologygroup.com', + 'washingtonpost.com', + ] + + const robotsTxtAllows = Driver.checkRobots + if (!String.prototype.endsWith) { + String.prototype.endsWith = function (searchString, position) { + const subjectString = this.toString() + if ( + typeof position !== 'number' || + !isFinite(position) || + Math.floor(position) !== position || + position > subjectString.length + ) { + position = subjectString.length + } + position -= searchString.length + const lastIndex = subjectString.indexOf(searchString, position) + return lastIndex !== -1 && lastIndex === position + } + } + + function getFrame(getFrameDetails, callback) { + chrome.webNavigation.getFrame(getFrameDetails, callback) + } + + function ifTrackingEnabled(details, ifCallback, elseCallback) { + const fullIfCallback = function () { + allowedByRobotsTxt(details, ifCallback, elseCallback) + } + + Utils.getOption('tracking', true).then(function (tracking) { + if (tracking) { + fullIfCallback() + } else { + elseCallback() + } + }) + } + + function allowedByRobotsTxt(details, ifCallback, elseCallback) { + if (details.url && !details.url.startsWith('chrome://')) { + Driver.checkRobots(details.url, details.url.startsWith('https:')) + .then(ifCallback) + .catch(elseCallback) + } else { + elseCallback() + } + } + + function isPixelRequest(request) { + return ( + (request.type === 'image' || request.responseStatus === 204) && + request.size <= 1000 + ) + } + + function isVpaidOrVastRequest(request) { + const lowerCaseUrl = request.url.toLowerCase() + return lowerCaseUrl.includes('vpaid') || lowerCaseUrl.includes('vast') + } + + function hasValidRequestType(request) { + return whitelistReqTypes.includes(request.type) + } + + function stripQueryParams(url) { + return url.split('?', 1)[0] + } + + function parseHostnameFromUrl(url) { + const parser = document.createElement('a') + parser.href = url + return parser.hostname + } + + function hasDomain(url, domain) { + return parseHostnameFromUrl(url).endsWith(domain) + } + + function findHeader(headers, key) { + let header + for (let i = 0; i < headers.length; i += 1) { + header = headers[i] + if (header.name.toLowerCase() === key) { + return header + } + } + return null + } + + function validVideoType(vtype) { + const goodType = videoContentTypesPrefixes.some(function (prefix) { + return vtype.indexOf(prefix) === 0 + }) + return goodType + } + + function assetMsgKey(assetReq) { + const url = stripQueryParams(assetReq.url) + const key = assetReq.frameId + '-' + url + return key + } + + const PageNetworkTrafficCollector = function (tabId) { + this.tabId = tabId + this.displayAdFound = false + this.requests = {} + this.msgsBeingSent = {} + this.assetsSeen = {} + this.allRedirects = {} + } + + var globalPageContainer = { + collectors: {}, + dyingCollectors: {}, + + cleanupCollector(tabId) { + if (tabId in this.collectors) { + delete globalPageContainer.collectors[tabId] + } + }, + + onNewNavigation(details) { + const tabId = details.tabId + this.cleanupCollector(tabId) + + ifTrackingEnabled( + details, + function () { + if (!areListenersRegistered) { + registerListeners() + } + this.collectors[tabId] = new PageNetworkTrafficCollector(tabId) + }.bind(this), + function () { + if (areListenersRegistered) { + unregisterListeners() + } + } + ) + }, + + onNavigationCommitted(details) {}, + + onNavigationCompleted(details) {}, + + onTabClose(tabId, closeInfo) { + this.cleanupCollector(tabId) + delete this.collectors[tabId] + }, + + onDisplayAdFound(tabId) { + this.collectors[tabId].displayAdFound = true + }, + + getRandId() { + return String(Math.floor(Math.random() * 1e9)) + }, + + getCollector(tabId) { + if (this.collectors.hasOwnProperty(tabId)) { + return this.collectors[tabId] + } + return null + }, + + forwardCall(details, collectorMemberFunction) { + const collector = this.getCollector(details.tabId) + if (collector !== null) { + collectorMemberFunction.apply(collector, [details]) + } + }, + } + + PageNetworkTrafficCollector.prototype.sendLogMessageToTabConsole = function () { + const logMessage = Array.from(arguments).join(' ') + const message = { message: logMessage, event: 'console-log-message' } + chrome.tabs.sendMessage(this.tabId, message) + } + + PageNetworkTrafficCollector.prototype.sendToTab = function ( + assetReq, + reqs, + curPageUrl, + adTrackingEvent + ) { + const msg = {} + msg.assets = [] + msg.requests = [] + msg.event_data = {} + msg.event = adTrackingEvent + if (adTrackingEvent === 'new-video-ad') { + msg.requests = reqs + msg.requests.sort(function (reqA, reqB) { + return reqA.requestTimestamp - reqB.requestTimestamp + }) + if (assetReq) { + msg.assets = [assetReq] + } + } else if (adTrackingEvent === 'new-invalid-video-ad') { + msg.requests = reqs.map(function (request) { + return parseHostnameFromUrl(request.url) + }) + msg.assets = [ + { + url: parseHostnameFromUrl(assetReq.url), + + contentType: assetReq.contentType, + size: assetReq.size, + }, + ] + } + msg.origUrl = curPageUrl + msg.displayAdFound = this.displayAdFound + + chrome.tabs.sendMessage(this.tabId, msg) + } + + PageNetworkTrafficCollector.prototype.getRedirKey = function (url, frameId) { + return url + ':' + frameId + } + + PageNetworkTrafficCollector.prototype.seenBefore = function (request) { + const oldTime = this.assetsSeen[assetMsgKey(request)] + if (oldTime && request.requestTimestamp - oldTime < secBetweenDupAssets) { + return true + } + return false + } + + PageNetworkTrafficCollector.prototype.recordSeenAsset = function (request) { + this.assetsSeen[assetMsgKey(request)] = request.requestTimestamp + } + + PageNetworkTrafficCollector.prototype.onBeforeRequest = function (details) { + const req = { + url: details.url, + type: details.type, + httpMethod: details.method, + frameId: details.frameId, + parentFrameId: details.parentFrameId, + requestTimestamp: details.timeStamp, + } + this.requests[details.requestId] = req + } + + PageNetworkTrafficCollector.prototype.onSendHeaders = function (details) { + let request, header + request = this.requests[details.requestId] + header = request && findHeader(details.requestHeaders, 'x-requested-with') + if (header && header.value.toLowerCase().includes('flash')) { + request.from_flash = true + } + } + + PageNetworkTrafficCollector.prototype.onHeadersReceived = function (details) { + const getFrameDetails = { + tabId: details.tabId, + processId: null, + frameId: details.frameId, + } + const pageNetworkTrafficController = this + getFrame(getFrameDetails, function (frameDetails) { + if (frameDetails && frameDetails.url) { + pageNetworkTrafficController._onHeadersReceived(details, frameDetails) + } + }) + } + + PageNetworkTrafficCollector.prototype._onHeadersReceived = function ( + details, + frameDetails + ) { + let contentSize, contentRange + + const request = this.requests[details.requestId] + if (request) { + const redirParent = this.allRedirects[ + this.getRedirKey(details.url, details.frameId) + ] + let header = + request && findHeader(details.responseHeaders, 'content-type') + const contentType = header && header.value.toLowerCase() + + if (contentType) { + request.contentType = contentType + } + header = request && findHeader(details.responseHeaders, 'content-length') + contentSize = header && header.value + if (contentSize) { + request.size = request.size || 0 + request.size += parseInt(contentSize) + } + header = request && findHeader(details.responseHeaders, 'content-range') + contentRange = header && header.value + if (contentRange) { + request.contentRange = parseInt(contentRange.split('/')[1]) + } + + let frameUrl = null + if (frameDetails && frameDetails.url) { + frameUrl = frameDetails.url + } + if ( + !this.bannedRequest(request) && + (this.isVideoReq(frameUrl, request) || + (redirParent && redirParent.isVideo)) + ) { + request.isVideo = true + } + } + } + + PageNetworkTrafficCollector.prototype.onBeforeRedirect = function (details) { + const request = this.requests[details.requestId] + if (request) { + if (request.redirects) { + request.redirects.push(details.redirectUrl) + } else { + request.redirects = [details.redirectUrl] + } + this.allRedirects[ + this.getRedirKey(details.redirectUrl, details.frameId) + ] = request + } + } + + PageNetworkTrafficCollector.prototype.isYoutubeMastheadRequest = function ( + url + ) { + const re = /video_masthead/ + return this.hasYoutubeDomain(url) && re.test(url) + } + PageNetworkTrafficCollector.prototype.isYoutubeVideoRequest = function ( + srcUrl, + destUrl + ) { + if (!this.hasYoutubeDomain(srcUrl)) { + return false + } + + const re = /https?:\/\/r.*?\.googlevideo\.com\/videoplayback\?/ + return re.test(destUrl) + } + PageNetworkTrafficCollector.prototype.processResponse = function ( + requestDetails, + frameDetails + ) { + let request + if (requestDetails) { + request = this.requests[requestDetails.requestId] + if (request) { + request.responseStatus = requestDetails.statusCode + request.responseTimestamp = requestDetails.timeStamp + + let frameUrl = null + if (frameDetails && frameDetails.url) { + frameUrl = frameDetails.url + } + + let requestUrl = null + if (request.url) { + requestUrl = request.url + } + + if (this.isYoutubeAdReq(frameUrl, requestUrl)) { + const destVideoId = this.parseYoutubeVideoIdFromUrl(requestUrl) + const srcVideoId = this.parseYoutubeVideoIdFromUrl(frameUrl) + if (srcVideoId && destVideoId) { + request.isYoutubeAd = true + request.isVideo = true + request.rawSrcUrl = frameUrl + request.rawDestUrl = requestUrl + request.url = + 'https://www.youtube.com/watch?v=' + + this.parseYoutubeVideoIdFromUrl(requestUrl) + } + } else if ( + !this.bannedRequest(request) && + (this.isVideo || this.isVideoReq(frameUrl, request)) + ) { + request.isVideo = true + } + + if (request.isVideo) { + const msgKey = assetMsgKey(request) + this.msgsBeingSent[msgKey] = request + if (!this.seenBefore(request)) { + this.sendMsgWhenQuiet(msgKey) + } + this.recordSeenAsset(request) + } + } + } + } + + PageNetworkTrafficCollector.prototype.onResponseStarted = function ( + responseDetails + ) { + if (responseDetails.frameId < 0) { + responseDetails.frameId = 99999 + } + const getFrameDetails = { + tabId: responseDetails.tabId, + processId: null, + frameId: responseDetails.frameId, + } + const pageNetworkTrafficController = this + getFrame(getFrameDetails, function (frameDetails) { + if (frameDetails && frameDetails.url) { + pageNetworkTrafficController.processResponse( + responseDetails, + frameDetails + ) + } + }) + } + + PageNetworkTrafficCollector.prototype.hasBannedFiletype = function (request) { + const url = stripQueryParams(request.url) + if (bannedFiletypesReg.exec(url)) { + return true + } else { + return false + } + } + + PageNetworkTrafficCollector.prototype.checkContentHeaders = function ( + request + ) { + if (request.contentType && validVideoType(request.contentType)) { + return true + } + return false + } + + PageNetworkTrafficCollector.prototype.checkUrlExtension = function (request) { + const url = stripQueryParams(request.url) + if (extensionsReg.exec(url)) { + return true + } else { + return false + } + } + + PageNetworkTrafficCollector.prototype.isVideoReq = function ( + srcUrl, + request + ) { + if (this.isYoutubeVideoRequest(srcUrl, request.url)) { + return false + } + return this.checkUrlExtension(request) || this.checkContentHeaders(request) + } + PageNetworkTrafficCollector.prototype.hasYoutubeDomain = function (url) { + const hostname = parseHostnameFromUrl(url) + if (hostname === 'www.youtube.com') { + return true + } + return false + } + PageNetworkTrafficCollector.prototype.parseYoutubeVideoIdFromUrl = function ( + url + ) { + let re = /^https?:\/\/www\.youtube\.com\/get_video_info.*(?:\?|&)video_id=(.*?)(?:$|&)/ + let match = re.exec(url) + if (match && match.length > 1) { + return match[1] + } + + re = /^https?:\/\/www\.youtube\.com\/embed\/(.*?)(?:$|\?)/ + match = re.exec(url) + if (match && match.length > 1) { + return match[1] + } + + re = /^https?:\/\/www\.youtube\.com\/watch.*(\?|&)v=([^&]*)/ + match = re.exec(url) + if (match && match.length > 1) { + return match[1] + } + return null + } + + PageNetworkTrafficCollector.prototype.isYoutubeGetVideoInfoReq = function ( + url + ) { + const re = /^https?:\/\/www\.youtube\.com\/get_video_info\?/ + return re.test(url) + } + PageNetworkTrafficCollector.prototype.isYoutubeAdReq = function ( + srcUrl, + destUrl + ) { + if ( + !this.hasYoutubeDomain(srcUrl) || + !this.isYoutubeGetVideoInfoReq(destUrl) + ) { + return false + } + if ( + this.parseYoutubeVideoIdFromUrl(srcUrl) === + this.parseYoutubeVideoIdFromUrl(destUrl) && + !this.isYoutubeMastheadRequest(destUrl) + ) { + return false + } + return true + } + + PageNetworkTrafficCollector.prototype.bannedRequest = function (request) { + return ( + this.bannedVideoType(request) || + this.hasBannedFiletype(request) || + this.bannedVideoSize(request) + ) + } + + PageNetworkTrafficCollector.prototype.bannedVideoType = function (request) { + let badType = false + if (request.contentType) { + badType = bannedContentTypes.some(function (prefix) { + return request.contentType.includes(prefix) + }) + } + return badType + } + + PageNetworkTrafficCollector.prototype.bannedVideoSize = function (request) { + if (request.size !== null) { + if ( + request.size < minVidSize || + request.size > maxVidSize || + request.contentRange > maxContentRange + ) { + return true + } + } + return false + } + + PageNetworkTrafficCollector.prototype.grabTagReqs = function ( + tabRequests, + assetRequest + ) { + let minTimestamp, maxTimestamp + minTimestamp = assetRequest.requestTimestamp - secBefore + maxTimestamp = assetRequest.requestTimestamp + secAfter + + const filteredRequests = tabRequests.filter(function (request) { + return ( + request.requestTimestamp > minTimestamp && + request.requestTimestamp < maxTimestamp && + request.frameId === assetRequest.frameId && + request.url !== assetRequest.url && + (hasValidRequestType(request) || isPixelRequest(request)) + ) + }) + + return filteredRequests + } + + PageNetworkTrafficCollector.prototype.isValidVideoAd = function ( + assetRequest, + tagRequests + ) { + const hasVpaidOrVastRequest = tagRequests.some(function (tagRequest) { + return isVpaidOrVastRequest(tagRequest) + }) + + if (assetRequest.isYoutubeAd) { + return true + } + if (hasVpaidOrVastRequest) { + return true + } + const hasTopVideoAssetDomain = topVideoAssetDomains.some(function ( + assetDomain + ) { + return hasDomain(assetRequest.url, assetDomain) + }) + + return hasTopVideoAssetDomain + } + + PageNetworkTrafficCollector.prototype.sendMsgWhenQuiet = function (msgKey) { + const _this = this + let origPageUrl + let msgAssetReq + msgAssetReq = this.msgsBeingSent[msgKey] + chrome.tabs.get(this.tabId, function (tab) { + origPageUrl = tab.url + }) + + setTimeout(function () { + const rawRequests = [] + if (globalPageContainer.collectors[_this.tabId] === _this) { + for (const reqId in _this.requests) { + rawRequests.push(_this.requests[reqId]) + } + const tagReqs = _this.grabTagReqs(rawRequests, msgAssetReq) + + if (_this.isValidVideoAd(msgAssetReq, tagReqs)) { + _this.sendToTab(msgAssetReq, tagReqs, origPageUrl, 'new-video-ad') + } else { + _this.sendToTab( + msgAssetReq, + tagReqs, + origPageUrl, + 'new-invalid-video-ad' + ) + } } else { - elseCallback(); } - }); - - } - - function allowedByRobotsTxt(details, ifCallback, elseCallback) { - if ( details.url && !details.url.startsWith('chrome://') ) { - Driver.checkRobots(details.url, details.url.startsWith('https:')).then(ifCallback).catch(elseCallback); - } else { - elseCallback(); - } - } - - function isPixelRequest(request) { - return (request.type === 'image' || request.responseStatus === 204) && - request.size <= 1000; - } - - function isVpaidOrVastRequest(request) { - var lowerCaseUrl = request.url.toLowerCase(); - return lowerCaseUrl.indexOf('vpaid') !== -1 || lowerCaseUrl.indexOf('vast') !== -1; - } - - function hasValidRequestType(request) { - return whitelistReqTypes.indexOf(request.type) >= 0; - } - - function stripQueryParams(url) { - return url.split('?', 1)[0]; - } - - function parseHostnameFromUrl(url) { - var parser = document.createElement('a'); - parser.href = url; - return parser.hostname; - } - - function hasDomain(url, domain) { - return parseHostnameFromUrl(url).endsWith(domain); - } - - function findHeader(headers, key) { - var header; - for ( var i = 0; i < headers.length; i += 1 ) { - header = headers[i]; - if ( header.name.toLowerCase() === key ) { - return header; - } - } - return null; - } - - function validVideoType(vtype) { - var goodType = videoContentTypesPrefixes.some(function(prefix) { - return vtype.indexOf(prefix) === 0; - }); - return goodType; - } - - function assetMsgKey(assetReq) { - var url = stripQueryParams(assetReq.url); - var key = assetReq.frameId + '-' + url; - return key; - } - - var PageNetworkTrafficCollector = function(tabId) { - this.tabId = tabId; - this.displayAdFound = false; - this.requests = {}; - this.msgsBeingSent = {}; - this.assetsSeen = {}; - this.allRedirects = {}; - }; - - var globalPageContainer = { - collectors: {}, - dyingCollectors: {}, - - cleanupCollector: function(tabId) { - if ( tabId in this.collectors ) { - delete globalPageContainer.collectors[tabId]; - } - }, - - onNewNavigation: function(details) { - var tabId = details.tabId; - this.cleanupCollector(tabId); - - ifTrackingEnabled( - details, - function() { - if ( !areListenersRegistered ) { - - registerListeners(); - } - this.collectors[tabId] = new PageNetworkTrafficCollector(tabId); - }.bind(this), - function() { - if ( areListenersRegistered ) { - - unregisterListeners(); - } - } - ); - }, - - onNavigationCommitted: function(details) { - - }, - - onNavigationCompleted: function(details) { - - }, - - onTabClose: function(tabId, closeInfo) { - - this.cleanupCollector(tabId); - delete this.collectors[tabId]; - }, - - onDisplayAdFound: function(tabId) { - this.collectors[tabId].displayAdFound = true; - }, - - getRandId: function() { - return String(Math.floor(Math.random() * 1e9)); - }, - - getCollector: function(tabId) { - if ( this.collectors.hasOwnProperty(tabId) ) { - return this.collectors[tabId]; - } - return null; - }, - - forwardCall: function(details, collectorMemberFunction) { - var collector = this.getCollector(details.tabId); - if ( collector !== null ) { - collectorMemberFunction.apply(collector, [details]); - } - } - }; - - PageNetworkTrafficCollector.prototype.sendLogMessageToTabConsole = function() { - var logMessage = Array.from(arguments).join(' '); - var message = {message: logMessage, event: 'console-log-message'}; - chrome.tabs.sendMessage(this.tabId, message); - }; - - PageNetworkTrafficCollector.prototype.sendToTab = function(assetReq, reqs, curPageUrl, adTrackingEvent) { - var msg = {}; - msg.assets = []; - msg.requests = []; - msg.event_data = {}; - msg.event = adTrackingEvent; - if ( adTrackingEvent === 'new-video-ad' ) { - msg.requests = reqs; - msg.requests.sort(function(reqA, reqB) {return reqA.requestTimestamp - reqB.requestTimestamp;}); - if ( assetReq ) { - msg.assets = [assetReq]; - } - } else if ( adTrackingEvent === 'new-invalid-video-ad' ) { - msg.requests = reqs.map(function(request) { - return parseHostnameFromUrl(request.url); - }); - msg.assets = [{ - - url: parseHostnameFromUrl(assetReq.url), - - contentType: assetReq.contentType, - size: assetReq.size - }]; - } - msg.origUrl = curPageUrl; - msg.displayAdFound = this.displayAdFound; - - chrome.tabs.sendMessage(this.tabId, msg); - }; - - PageNetworkTrafficCollector.prototype.getRedirKey = function(url, frameId) { - return url + ':' + frameId; - }; - - PageNetworkTrafficCollector.prototype.seenBefore = function(request) { - var oldTime = this.assetsSeen[assetMsgKey(request)]; - if ( oldTime && (request.requestTimestamp-oldTime < secBetweenDupAssets)){ - - return true; - } - return false; - }; - - PageNetworkTrafficCollector.prototype.recordSeenAsset = function(request) { - this.assetsSeen[assetMsgKey(request)] = request.requestTimestamp; - }; - - PageNetworkTrafficCollector.prototype.onBeforeRequest = function(details) { - var req = { - url: details.url, - type: details.type, - httpMethod: details.method, - frameId: details.frameId, - parentFrameId: details.parentFrameId, - requestTimestamp: details.timeStamp, - }; - this.requests[details.requestId] = req; - }; - - PageNetworkTrafficCollector.prototype.onSendHeaders = function(details) { - var request, header; - request = this.requests[details.requestId]; - header = request && findHeader(details.requestHeaders, 'x-requested-with'); - if ( header && header.value.toLowerCase().indexOf('flash') > -1 ) { - request.from_flash = true; - } - }; - - PageNetworkTrafficCollector.prototype.onHeadersReceived = function(details) { - var getFrameDetails = { - tabId: details.tabId, - processId: null, - frameId: details.frameId - }; - var pageNetworkTrafficController = this; - getFrame(getFrameDetails, function(frameDetails) { - if ( frameDetails && frameDetails.url ) { - pageNetworkTrafficController._onHeadersReceived(details, frameDetails); - } - }); - }; - - PageNetworkTrafficCollector.prototype._onHeadersReceived = function(details, frameDetails) { - var contentSize, contentRange; - - var request = this.requests[details.requestId]; - if ( request ) { - var redirParent = this.allRedirects[this.getRedirKey(details.url, details.frameId)]; - var header = request && findHeader(details.responseHeaders, 'content-type'); - var contentType = header && header.value.toLowerCase(); - - if ( contentType){ - request.contentType = contentType; - } - header = request && findHeader(details.responseHeaders, 'content-length'); - contentSize = header && header.value; - if ( contentSize ) { - request.size = request.size || 0; - request.size += parseInt(contentSize); - } - header = request && findHeader(details.responseHeaders, 'content-range'); - contentRange = header && header.value; - if ( contentRange ) { - request.contentRange = parseInt(contentRange.split('/')[1]); - } - - var frameUrl = null; - if ( frameDetails && frameDetails.url ) { - frameUrl = frameDetails.url; - } - if ( !this.bannedRequest(request) && - (this.isVideoReq(frameUrl, request) || (redirParent && redirParent.isVideo))) { - request.isVideo = true; - } - } - }; - - PageNetworkTrafficCollector.prototype.onBeforeRedirect = function(details) { - var request = this.requests[details.requestId]; - if ( request ) { - if ( request.redirects ) { - request.redirects.push(details.redirectUrl); - } else { - request.redirects = [details.redirectUrl]; - } - this.allRedirects[this.getRedirKey(details.redirectUrl, details.frameId)] = request; - } - }; - - PageNetworkTrafficCollector.prototype.isYoutubeMastheadRequest = function(url) { - var re = /video_masthead/; - return this.hasYoutubeDomain(url) && re.test(url); - }; - PageNetworkTrafficCollector.prototype.isYoutubeVideoRequest = function(srcUrl, destUrl) { - if ( !this.hasYoutubeDomain(srcUrl) ) { - return false; - } - - var re = /https?:\/\/r.*?\.googlevideo\.com\/videoplayback\?/; - return re.test(destUrl); - }; - PageNetworkTrafficCollector.prototype.processResponse = function(requestDetails, frameDetails) { - var request; - if ( requestDetails ) { - request = this.requests[requestDetails.requestId]; - if ( request ) { - request.responseStatus = requestDetails.statusCode; - request.responseTimestamp = requestDetails.timeStamp; - - var frameUrl = null; - if ( frameDetails && frameDetails.url ) { - frameUrl = frameDetails.url; - } - - var requestUrl = null; - if ( request.url ) { - requestUrl = request.url; - } - - if ( this.isYoutubeAdReq(frameUrl, requestUrl) ) { - var destVideoId = this.parseYoutubeVideoIdFromUrl(requestUrl); - var srcVideoId = this.parseYoutubeVideoIdFromUrl(frameUrl); - if ( srcVideoId && destVideoId ) { - request.isYoutubeAd = true; - request.isVideo = true; - request.rawSrcUrl = frameUrl; - request.rawDestUrl = requestUrl; - request.url = 'https://www.youtube.com/watch?v=' + this.parseYoutubeVideoIdFromUrl(requestUrl); - } - } else if ( !this.bannedRequest(request) && - (this.isVideo || this.isVideoReq(frameUrl, request))) { - request.isVideo = true; - } - - if ( request.isVideo ) { - - var msgKey = assetMsgKey(request); - this.msgsBeingSent[msgKey] = request; - if ( !this.seenBefore(request) ) { - this.sendMsgWhenQuiet(msgKey); - } - this.recordSeenAsset(request); - } - } - } - }; - - PageNetworkTrafficCollector.prototype.onResponseStarted = function(responseDetails) { - if ( responseDetails.frameId < 0 ) { - responseDetails.frameId = 99999; - - } - var getFrameDetails = { - tabId: responseDetails.tabId, - processId: null, - frameId: responseDetails.frameId - }; - var pageNetworkTrafficController = this; - getFrame(getFrameDetails, function(frameDetails) { - if ( frameDetails && frameDetails.url ) { - pageNetworkTrafficController.processResponse(responseDetails, frameDetails); - } - }); - }; - - PageNetworkTrafficCollector.prototype.hasBannedFiletype = function(request) { - var url = stripQueryParams(request.url); - if ( bannedFiletypesReg.exec(url) ) { - return true; - } else { - return false; - } - }; - - PageNetworkTrafficCollector.prototype.checkContentHeaders = function(request) { - if ( request.contentType && validVideoType(request.contentType) ) { - return true; - } - return false; - }; - - PageNetworkTrafficCollector.prototype.checkUrlExtension = function(request) { - var url = stripQueryParams(request.url); - if ( extensionsReg.exec(url) ) { - return true; - } else { - return false; - } - }; - - PageNetworkTrafficCollector.prototype.isVideoReq = function(srcUrl, request) { - if ( this.isYoutubeVideoRequest(srcUrl, request.url) ) { - return false; - } - return this.checkUrlExtension(request) || this.checkContentHeaders(request); - }; - PageNetworkTrafficCollector.prototype.hasYoutubeDomain = function(url) { - var hostname = parseHostnameFromUrl(url) ; - if ( hostname === 'www.youtube.com' ) { - return true; - } - return false; - }; - PageNetworkTrafficCollector.prototype.parseYoutubeVideoIdFromUrl = function(url) { - var re = /^https?:\/\/www\.youtube\.com\/get_video_info.*(?:\?|&)video_id=(.*?)(?:$|&)/; - var match = re.exec(url); - if ( match && match.length > 1 ) { - return match[1]; - } - - re = /^https?:\/\/www\.youtube\.com\/embed\/(.*?)(?:$|\?)/; - match = re.exec(url); - if ( match && match.length > 1 ) { - return match[1]; - } - - re = /^https?:\/\/www\.youtube\.com\/watch.*(\?|&)v=([^&]*)/; - match = re.exec(url); - if ( match && match.length > 1 ) { - return match[1]; - } - return null; - }; - - PageNetworkTrafficCollector.prototype.isYoutubeGetVideoInfoReq = function(url) { - var re = /^https?:\/\/www\.youtube\.com\/get_video_info\?/; - return re.test(url); - }; - PageNetworkTrafficCollector.prototype.isYoutubeAdReq = function(srcUrl, destUrl) { - - if ( !this.hasYoutubeDomain(srcUrl) || - !this.isYoutubeGetVideoInfoReq(destUrl)) { - return false; - } - if ( this.parseYoutubeVideoIdFromUrl(srcUrl) === - this.parseYoutubeVideoIdFromUrl(destUrl) && - !this.isYoutubeMastheadRequest(destUrl)) { - return false; - } - return true; - }; - - PageNetworkTrafficCollector.prototype.bannedRequest = function(request) { - return this.bannedVideoType(request) || this.hasBannedFiletype(request) || this.bannedVideoSize(request); - }; - - PageNetworkTrafficCollector.prototype.bannedVideoType = function(request) { - var badType = false; - if ( request.contentType ) { - badType = bannedContentTypes.some(function(prefix) { - return request.contentType.indexOf(prefix) >= 0; - }); - } - return badType; - }; - - PageNetworkTrafficCollector.prototype.bannedVideoSize = function(request) { - if ( request.size !== null ) { - if ( request.size < minVidSize || request.size > maxVidSize || request.contentRange > maxContentRange ) { - return true; - } - } - return false; - }; - - PageNetworkTrafficCollector.prototype.grabTagReqs = function(tabRequests, assetRequest) { - var minTimestamp, maxTimestamp; - minTimestamp = assetRequest.requestTimestamp - secBefore; - maxTimestamp = assetRequest.requestTimestamp + secAfter; - - var filteredRequests = tabRequests.filter(function(request) { - return (request.requestTimestamp > minTimestamp && - request.requestTimestamp < maxTimestamp && - request.frameId === assetRequest.frameId && - request.url !== assetRequest.url && - (hasValidRequestType(request) || - isPixelRequest(request))); - }); - - return filteredRequests; - }; - - PageNetworkTrafficCollector.prototype.isValidVideoAd = function(assetRequest, tagRequests) { - var hasVpaidOrVastRequest = tagRequests.some(function(tagRequest) { - return isVpaidOrVastRequest(tagRequest); - }); - - if ( assetRequest.isYoutubeAd ) { - return true; - } - if ( hasVpaidOrVastRequest ) { - return true; - } - var hasTopVideoAssetDomain = topVideoAssetDomains.some(function(assetDomain) { - return hasDomain(assetRequest.url, assetDomain); - }); - - return hasTopVideoAssetDomain; - }; - - PageNetworkTrafficCollector.prototype.sendMsgWhenQuiet = function(msgKey) { - var _this = this, - origPageUrl, msgAssetReq; - msgAssetReq = this.msgsBeingSent[msgKey]; - chrome.tabs.get(this.tabId, function(tab) { - origPageUrl = tab.url; - }); - - setTimeout(function() { - var rawRequests = []; - if ( globalPageContainer.collectors[_this.tabId] === _this ) { - for ( var reqId in _this.requests ) { - rawRequests.push(_this.requests[reqId]); - } - var tagReqs = _this.grabTagReqs(rawRequests, msgAssetReq); - - if ( _this.isValidVideoAd(msgAssetReq, tagReqs) ) { - _this.sendToTab(msgAssetReq, tagReqs, origPageUrl, 'new-video-ad'); - } else { - - _this.sendToTab(msgAssetReq, tagReqs, origPageUrl, 'new-invalid-video-ad'); - } - - } else { - - } - delete _this.msgsBeingSent[msgKey]; - }, secAfter+secBefore); - }; - - PageNetworkTrafficCollector.prototype.existingMessage = function(candidateRequest) { - var frameMsg = this.msgsBeingSent[candidateRequest.frameId]; - if ( frameMsg ) { - return frameMsg; - } else { - return null; - } - }; - - function onBeforeRequestListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onBeforeRequest); - } - - function onSendHeadersListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onSendHeaders); - } - - function onHeadersReceivedListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onHeadersReceived); - } - - function onBeforeRedirectListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onBeforeRedirect); - } - - function onResponseStartedListener(details) { - globalPageContainer.forwardCall(details, PageNetworkTrafficCollector.prototype.onResponseStarted); - } - - function onCommittedListener(details) { - if ( details.frameId === 0 ) { - globalPageContainer.onNavigationCommitted(details); - } - } - - function onCompletedListener(details) { - if ( details.frameId === 0 ) { - globalPageContainer.onNavigationCompleted(details); - } - } - - function onRemovedListener(tabId, closeInfo) { - globalPageContainer.onTabClose(tabId, closeInfo); - } - - function onMessageListener(message, sender, sendResponse) { - if ( message.event === 'new-ad' && message.data.event === 'ad' ) { - var tabId = sender.tab.id; - if ( tabId ) { - globalPageContainer.onDisplayAdFound(tabId); - } - } - } - - function registerListeners() { - - chrome.webRequest.onBeforeRequest.addListener( - onBeforeRequestListener, - {urls: ['http://*/*', 'https://*/*']}, - [] - ); - - chrome.webRequest.onSendHeaders.addListener( - onSendHeadersListener, - {urls: ['http://*/*', 'https://*/*']}, - ['requestHeaders'] - ); - - chrome.webRequest.onHeadersReceived.addListener( - onHeadersReceivedListener, - {urls: ['http://*/*', 'https://*/*']}, - ['responseHeaders'] - ); - - chrome.webRequest.onBeforeRedirect.addListener( - onBeforeRedirectListener, - {urls: ['http://*/*', 'https://*/*']}, - [] - ); - - chrome.webRequest.onResponseStarted.addListener( - onResponseStartedListener, - {urls: ['http://*/*', 'https://*/*']}, - ['responseHeaders'] - ); - - chrome.webNavigation.onCommitted.addListener(onCommittedListener); - chrome.webNavigation.onCompleted.addListener(onCompletedListener); - chrome.tabs.onRemoved.addListener(onRemovedListener); - chrome.runtime.onMessage.addListener(onMessageListener); - - areListenersRegistered = true; - } - - function unregisterListeners() { - - chrome.webRequest.onBeforeRequest.removeListener( - onBeforeRequestListener - ); - - chrome.webRequest.onSendHeaders.removeListener( - onSendHeadersListener - ); - - chrome.webRequest.onHeadersReceived.removeListener( - onHeadersReceivedListener - ); - - chrome.webRequest.onBeforeRedirect.removeListener( - onBeforeRedirectListener - ); - - chrome.webRequest.onResponseStarted.removeListener( - onResponseStartedListener - ); - - chrome.webNavigation.onCommitted.removeListener(onCommittedListener); - chrome.webNavigation.onCompleted.removeListener(onCompletedListener); - chrome.tabs.onRemoved.removeListener(onRemovedListener); - chrome.runtime.onMessage.removeListener(onMessageListener); - areListenersRegistered = false; - } + delete _this.msgsBeingSent[msgKey] + }, secAfter + secBefore) + } + + PageNetworkTrafficCollector.prototype.existingMessage = function ( + candidateRequest + ) { + const frameMsg = this.msgsBeingSent[candidateRequest.frameId] + if (frameMsg) { + return frameMsg + } else { + return null + } + } + + function onBeforeRequestListener(details) { + globalPageContainer.forwardCall( + details, + PageNetworkTrafficCollector.prototype.onBeforeRequest + ) + } + + function onSendHeadersListener(details) { + globalPageContainer.forwardCall( + details, + PageNetworkTrafficCollector.prototype.onSendHeaders + ) + } + + function onHeadersReceivedListener(details) { + globalPageContainer.forwardCall( + details, + PageNetworkTrafficCollector.prototype.onHeadersReceived + ) + } + + function onBeforeRedirectListener(details) { + globalPageContainer.forwardCall( + details, + PageNetworkTrafficCollector.prototype.onBeforeRedirect + ) + } + + function onResponseStartedListener(details) { + globalPageContainer.forwardCall( + details, + PageNetworkTrafficCollector.prototype.onResponseStarted + ) + } + + function onCommittedListener(details) { + if (details.frameId === 0) { + globalPageContainer.onNavigationCommitted(details) + } + } + + function onCompletedListener(details) { + if (details.frameId === 0) { + globalPageContainer.onNavigationCompleted(details) + } + } + + function onRemovedListener(tabId, closeInfo) { + globalPageContainer.onTabClose(tabId, closeInfo) + } + + function onMessageListener(message, sender, sendResponse) { + if (message.event === 'new-ad' && message.data.event === 'ad') { + const tabId = sender.tab.id + if (tabId) { + globalPageContainer.onDisplayAdFound(tabId) + } + } + } + + function registerListeners() { + chrome.webRequest.onBeforeRequest.addListener( + onBeforeRequestListener, + { urls: ['http://*/*', 'https://*/*'] }, + [] + ) + + chrome.webRequest.onSendHeaders.addListener( + onSendHeadersListener, + { urls: ['http://*/*', 'https://*/*'] }, + ['requestHeaders'] + ) + + chrome.webRequest.onHeadersReceived.addListener( + onHeadersReceivedListener, + { urls: ['http://*/*', 'https://*/*'] }, + ['responseHeaders'] + ) + + chrome.webRequest.onBeforeRedirect.addListener( + onBeforeRedirectListener, + { urls: ['http://*/*', 'https://*/*'] }, + [] + ) + + chrome.webRequest.onResponseStarted.addListener( + onResponseStartedListener, + { urls: ['http://*/*', 'https://*/*'] }, + ['responseHeaders'] + ) + + chrome.webNavigation.onCommitted.addListener(onCommittedListener) + chrome.webNavigation.onCompleted.addListener(onCompletedListener) + chrome.tabs.onRemoved.addListener(onRemovedListener) + chrome.runtime.onMessage.addListener(onMessageListener) + + areListenersRegistered = true + } + + function unregisterListeners() { + chrome.webRequest.onBeforeRequest.removeListener(onBeforeRequestListener) + + chrome.webRequest.onSendHeaders.removeListener(onSendHeadersListener) + + chrome.webRequest.onHeadersReceived.removeListener( + onHeadersReceivedListener + ) + + chrome.webRequest.onBeforeRedirect.removeListener(onBeforeRedirectListener) + + chrome.webRequest.onResponseStarted.removeListener( + onResponseStartedListener + ) + + chrome.webNavigation.onCommitted.removeListener(onCommittedListener) + chrome.webNavigation.onCompleted.removeListener(onCompletedListener) + chrome.tabs.onRemoved.removeListener(onRemovedListener) + chrome.runtime.onMessage.removeListener(onMessageListener) + areListenersRegistered = false + } chrome.webNavigation.onBeforeNavigate.addListener( - function(details) { - if ( details.frameId === 0 ) { - globalPageContainer.onNewNavigation(details); + function (details) { + if (details.frameId === 0) { + globalPageContainer.onNewNavigation(details) } }, { - url: [{urlMatches: 'http://*/*'}, {urlMatches: 'https://*/*'}] + url: [{ urlMatches: 'http://*/*' }, { urlMatches: 'https://*/*' }], } - ); + ) chrome.runtime.onMessage.addListener((message, sender, callback) => { - if ( message === 'is_tracking_enabled' ) { + if (message === 'is_tracking_enabled') { ifTrackingEnabled( sender.tab, - function() { - try {callback({'tracking_enabled': true});} - catch(err) {} }, - function() { - try {callback({'tracking_enabled': false});} - catch(err) {} } - ); - } - return true; - }); -})(); + function () { + try { + callback({ tracking_enabled: true }) + } catch (err) {} + }, + function () { + try { + callback({ tracking_enabled: false }) + } catch (err) {} + } + ) + } + return true + }) +})()