var ContextFeatures = {"website":"https:\/\/lotusdentalclinic.ca","username":"lotusdentalclinic.ca","autoid":"7c4d05b24be8f363c8ea02ad4fb7a06c4771d847fb1d2afae313ac99183cc5cc","xpath_thumb_pic":"HTML\/BODY[1]\/DIV[2]\/DIV[1]\/DIV[1]\/DIV[1]\/A[1]\/IMG[1]","xpath_thumb_title":"HTML\/BODY[1]\/DIV[6]\/DIV[1]\/DIV[1]\/H1[1]","xpath_thumb_price":"","show_visited_count":5,"xpath_display_container":"HTML\/BODY[1]\/DIV[15]","onsale_ribbon_text":null,"onsale_var_value":null,"onsale_var_name":null,"onsale_visit_min_count":0,"list_directory":1,"registration_datetime":"2018-11-24 05:56:15","owner_verified":0,"xpath_thumb_content":null,"xpath_thumb_detail":"HTML\/BODY[1]\/DIV[6]\/DIV[1]\/DIV[1]\/P[1]","feature_enabled_featured_text":1,"feature_enabled_popup":0,"feature_enabled_banner_horizontal_vertical":0,"feature_enabled_auto_sale":0}; function disableClicks(node) { // console.log("disableClicks: tagName=" + node.get(0).tagName + " src=" + node.get(0).src); console.log("disableClicks"); node.find("*").each(function(index) { console.log("disableClicks: tagName=" + $(this).get(0).tagName + " src=" + $(this).get(0).src); $(this).on("click", function(event) { event.preventDefault(); event.stopPropagation(); }); // Vick: I want to do this for buttons, so they can be processed recursively for all iframes // $(this).off(); // $(this).attr('disabled',true); }); // According to https://api.jquery.com/each/ I should be able to use implicit iteration: // contents.find("*").on("click", function(event) { // event.preventDefault(); // event.stopPropagation(); // }); } // this is the DOM object // $(this) is the JQuery element // index is the index in the array/collection being traversed // calling get(0) on a JQuery element gives you the DOM object // contents: // https://api.jquery.com/contents/ // iframe.contents returns ONLY THE IMMEDIATE CHILDREN of the iframe DOM, IF the iframe is on the SAME DOMAIN as the main page. function recursiveIFrameDisableClicks(iframe) { console.log('In recursiveIFrameDisableClicks, iframe src=' + iframe.attr('src')); disableClicks(iframe.contents()); iframe.contents().find("iframe").each(function(index) { console.log('recursiveIFrameDisableClicks: recursing'); recursiveIFrameDisableClicks($(this)); }); } function linksToAbsolute(base, relative) { var stack = base.split("/"), parts = relative.split("/"); stack.pop(); // remove current file name (or empty string) // (omit if "base" is the current folder without trailing slash) for (var i=0; i targetNode.style.setProperty(key, computedStyle.getPropertyValue(key), computedStyle.getPropertyPriority(key))); } function utlCopyNodeAttributes(sourceNode, targetNode) { /* this works */ var klass = sourceNode.getAttribute('class'); targetNode.setAttribute('class', klass); /* this is more comprehensive but copies both computed and non-computed attributes: for (var i = 0; i < sourceNode.attributes.length; i++) { targetNode.setAttribute(sourceNode.attributes[i].nodeName, sourceNode.attributes[i].nodeValue); } */ } function utlTagChange(el, tag, copyStyle = false, copyAttributes = false, copyContent = false){ var parent = el.parentNode ,newElem = document.createElement(tag); if( copyStyle ) { utlCopyNodeStyle(el, newElem); } if( copyAttributes ) { utlCopyNodeAttributes(el, newElem); } if( copyContent ) { newElem.innerHTML = el.innerHTML } parent.replaceChild(newElem, el); return newElem; } function utlInsertAfter(newNode, referenceNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); } function dictKeyCount(obj) { return Object.keys(obj).length === 0; } function isDictEmpty(obj) { return Object.keys(obj).length === 0; } var GlobalElementForUrlValidation; function isValidURL(u){ if('' == u) { return false; } if(!GlobalElementForUrlValidation){ GlobalElementForUrlValidation = document.createElement('input'); GlobalElementForUrlValidation.setAttribute('type', 'url'); } GlobalElementForUrlValidation.value = u; return GlobalElementForUrlValidation.validity.valid; } function addCSSRule(rule) { /* Some alternate solutions here: https://stackoverflow.com/questions/15505225/inject-css-stylesheet-as-string-using-javascript */ var css = document.createElement('style'); css.type = 'text/css'; if (css.styleSheet) css.styleSheet.cssText = rule; // Support for IE else css.appendChild(document.createTextNode(rule)); // Support for the rest document.getElementsByTagName("head")[0].appendChild(css); } /* BUG: For some reason this always referrs to http even if hosted on https */ function addCSSHref(href) { var head = document.getElementsByTagName('head')[0]; var link = document.createElement('link'); link.rel = 'stylesheet'; link.type = 'text/css'; link.href = href; link.media = 'all'; head.appendChild(link); } /* Example use: * walkDOM(hiddenProductPage, function(node) { * if( node.src ) { * console.log('node src=' + node.src); * if( 0 == node.src.indexOf("http://") ) { * console.log('found http src=' + node.src); * } * } * }); */ var walkDOM = function (node,func) { func(node); node = node.firstChild; while(node) { walkDOM(node,func); node = node.nextSibling; } }; function getAjax(url, funSuccess, funError=null) { console.log('getAjax:url=' + url); var request = new XMLHttpRequest(); request.open('GET', url, true); request.onreadystatechange = function() { if (this.readyState === 4) { if (this.status >= 200 && this.status < 400) { if(funSuccess) funSuccess(this, this.responseText); } else { if(funError) funError(this); } } }; request.overrideMimeType("text/html") request.send(); request = null; } // get dimensions of a hidden element. If an element is hidden, clientWidth/clientHeight will be zero. function getWidthHeight(elm) { // save away the values we'll change var opacity = elm.style.opacity; var position = elm.style.position; var display = elm.style.display; var visibility = elm.style.visibility; elm.style.opacity = '0'; elm.style.position = 'absolute'; elm.style.display = 'inline-block'; elm.style.visibility = 'visible'; var width = elm.clientWidth; var height = elm.clientHeight; // restore in reverse elm.style.visibility = visibility; elm.style.display = display; elm.style.position = position; elm.style.opacity = opacity; return [width, height]; } function getStyle(el, styleProp) { if (window.getComputedStyle) { var y = document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); } else if (el.currentStyle) { var y = el.currentStyle[styleProp]; } return y; } function fitText(container, startSize) { /* None of these solutions work well * https://css-tricks.com/viewport-sized-typography and https://css-tricks.com/fitting-text-to-a-container * container.style.fontSize = '1vw'; * Bottom solution: https://coderwall.com/p/_8jxgw/autoresize-text-to-fit-into-a-div-width-height */ container.style.fontSize = '100px'; while(container.scrollWidth > container.offsetWidth || container.scrollHeight > container.offsetHeight) { var newFontSize = (parseFloat( container.style.fontSize.slice(0, -2) ) * 0.90) + 'px'; container.style.fontSize = newFontSize; } } function logSizes(el, elName) { // https://stackoverflow.com/questions/21064101/understanding-offsetwidth-clientwidth-scrollwidth-and-height-respectively console.log(elName + ' actualWidth=' + getAbsoluteWidth(el) + ' actualHeight=' + getAbsoluteHeight(el)); console.log(elName + '.offsetWidth=' + el.offsetWidth + ' .offsetHeight=' + el.offsetHeight); console.log(elName + '.clientWidth=' + el.clientWidth + ' .clientHeight=' + el.clientHeight); console.log(elName + '.scrollWidth=' + el.scrollWidth + ' .scrollHeight=' + el.scrollHeight); var computedWidth = window.getComputedStyle(el)['width']; var computedHeight = window.getComputedStyle(el)['height']; console.log(elName + ' computedWidth=' + computedWidth + ' computedHeight=' + computedHeight); } function getAbsoluteWidth(el) { var styles = window.getComputedStyle(el); var margin = parseFloat(styles['marginLeft']) + parseFloat(styles['marginRight']); return Math.ceil(el.offsetWidth + margin); } function getAbsoluteHeight(el) { var styles = window.getComputedStyle(el); var margin = parseFloat(styles['marginTop']) + parseFloat(styles['marginBottom']); return Math.ceil(el.offsetHeight + margin); } function getFromStorage(varName) { var varValue; // var varValue = getCookie(varName); /* Local storage * pros: * a) doesnt have to load these extra cookie functions, * b) avoids sending the cookie values over the network. * c) allows more than 4k bytes per record (cookies limit) * cons: * doesnt send cookies over the network, so cannot do correlation analysis on the server * */ varValue = window.localStorage.getItem(varName); // console.log('getFromStorage:varName='+varName+' varValue='+varValue); return varValue; } function setToStorage(varName, varValue) { // console.log('setToStorage:varName='+varName+' varValue='+varValue); // return setCookie(varName, varValue); return window.localStorage.setItem(varName, varValue); } /* JavaScript Cookie v2.2.0 https://github.com/js-cookie/js-cookie had complex module loading logic which sometime wouldnt work. Now using http://www.quirksmode.org/js/cookies.html but had to encode values because cookies do not allow all sorts of chars (see https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie for details). */ function setCookie(name,value,days) { var expires = ""; var encodedValue; if( value && value.length ) { encodedValue = encodeURIComponent(value); } else { encodedValue = ''; } // console.log('setCookie:encodedValue='+encodedValue); if (days) { var date = new Date(); date.setTime(date.getTime() + (days*24*60*60*1000)); expires = "; expires=" + date.toUTCString(); } document.cookie = name + "=" + encodedValue + expires + "; path=/"; } function getCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) { var value = c.substring(nameEQ.length,c.length); var decodedValue = decodeURIComponent(value); // console.log('getCookie:decodedValue='+decodedValue); return decodedValue; } } return null; } function eraseCookie(name) { setCookie(name,'',0); } function poisonSetCookieFunction() { function setCookie(name, value, days) { console.log('This is a poisoned setCookie function call'); } } // Example: // VickDialog('Hi there!

I am a non modal alert message','','right:5px;bottom:5px;') // From https://codepen.io/oriadam/pen/doJqmR function createDivWithCss(wrapper, css, innerHtml, id, closeOnClick=false) { var div = document.createElement('div') div.style.cssText = css; div.innerHTML = innerHtml; div.setAttribute('id', id); if(closeOnClick) { div.addEventListener("click", function (event) { document.body.removeChild(wrapper); }, false); } wrapper.appendChild(div); return div; } function VickDialog(contentHtml, closeButtonText='', customCssWrapper='', id='', klass='') { // default css: var css = 'cursor:default;z-index:2147483646;overflow:hidden;whitespace:normal;font-weight:normal;max-width:none;max-height:none;min-width:10px;height:auto;min-height:10px;padding:0;margin:0;background:#f1f1f1;border:1px solid #333;border-color:#fff #333 #333 #fff;border-radius:1px;box-shadow:none;vertical-align:top' var textLength=0 const Xchar = '×'; // if not UTF-8, use 'x' var wrapper = document.createElement('div') wrapper.style.cssText = css + 'position:fixed;box-shadow:0 0 3px #000;padding:10px;width:auto;' + customCssWrapper if(id && id.length) wrapper.setAttribute('id', id); if(klass && klass.length) wrapper.setAttribute('class', klass); createDivWithCss(wrapper, css + 'position:static;width:auto;float:right;clear:both;border:none;margin-top:-10px;margin-right:-5px;font-size:18px;">', Xchar, 'oneX', closeOnClick=true); if(0 != closeButtonText.length) { createDivWithCss(wrapper, css + 'position:static;float:right;clear:both;margin:5px;padding:4px 10px;"', closeButtonText, 'twoCloseButton', closeOnClick=true); } if(0 != contentHtml.length) { createDivWithCss(wrapper, css + 'position:static;float:center;clear:both;margin:5px;padding:4px 10px;border:none;"', contentHtml, 'threeContent'); } document.body.appendChild(wrapper) return wrapper; } function displayInBanner(f, urlThumb, txtTitle, txtPrice, href, txtRibbon) { if( f.feature_enabled_banner_horizontal_vertical && f.xpath_display_container && f.xpath_display_container.length ) { // It's part of an existing banner/container, and at this stage we established it's going to be shown. Clear original content. In the future we may want to overlay on top of it. if( 0 == ContextThumbsShown ) { var clientWidth = ContextDisplay.clientWidth; // save it away var clientHeight = ContextDisplay.clientHeight; // save it away console.log('resetting ContextDisplay; clientWidth=' + clientWidth); ContextDisplay = utlTagChange(ContextDisplay, 'div', copyStyle = false, copyAttributes = false, copyContent = false); ContextDisplay.setAttribute('class', 'cp-row cp-mt-3 cp-mb-3'); if( clientWidth < clientHeight ) { // vertical banner ContextDisplay.style.width = clientWidth; } } } var colsPrice, colsRibbon; if( txtPrice.length && txtRibbon.length ) { colsPrice = 6; colsRibbon = 6; } else if (txtPrice && txtPrice.length ) { colsPrice = 12; colsRibbon = 0; } else if (txtRibbon && txtRibbon.length ) { colsPrice = 0; colsRibbon = 12; } else { colsPrice = 0; colsRibbon = 0; } ContextDisplay.innerHTML += `
` + txtPrice + `
` + txtRibbon + `
` + txtTitle + `
`; return true; } // start featured text function displayFeaturedText(f, urlThumb, txtTitle, txtDetail, txtPrice, href, txtRibbon) { // multiline string with backticks ContextDisplay.innerHTML = `

` + txtTitle + `

` + txtDetail + `

Read more

`; console.log('ContextDisplay.innerHTML=' + ContextDisplay.innerHTML); return true; } function createDisplay(f) { if( (f.feature_enabled_featured_text || f.feature_enabled_banner_horizontal_vertical) && (f.xpath_display_container && f.xpath_display_container.length) ) { var elms = getElementsByXPath(f.xpath_display_container, document); if( 1 != elms.length ) { console.log( 'xpath_display_container=' + f.xpath_display_container + ' found elms=' + JSON.stringify(elms) ); return false; } var elmSelected = elms[0]; if( f.feature_enabled_featured_text && f.xpath_thumb_detail && f.xpath_thumb_detail.length ) { ContextDisplay = document.createElement('div'); utlInsertAfter(ContextDisplay, elmSelected); return true; } if( f.feature_enabled_banner_horizontal_vertical ) { ContextDisplay = elmSelected; // Don't reset the banner contents here if there is nothing to display. Reset it upon the display of the first element. return true; } } if( f.feature_enabled_popup ) { // Start off as hidden, show only if there are items in it. ContextDisplay = VickDialog('', closeButtonText='', customCssWrapper='right:5px;bottom:5px;visibility:hidden;display:inline-flex;position:fixed', id='ContextPageModalId'); return true; } return false; } // Use http://youmightnotneedjquery.com . JQuery not allowed, as: // * either it's already included with a different version (with probability of conflict) // * or not included (so can't depend on it). function now() { return window.performance && window.performance.now && window.performance.timing && window.performance.timing.navigationStart ? window.performance.now() + window.performance.timing.navigationStart : Date.now(); } function parseHTML(str) { var el = document.createElement('div'); el.innerHTML = str; return el.children; }; function logObject(msg, obj) { // console.log(msg + JSON.stringify(obj, null, 4)); console.log(msg) console.log('%o',obj) } function onClickReportClickedContextDisplay(href) { // set cookie to be read on the server setCookie('ContextLastClicked', href, 1); // this is called by onClick, must return false return false; } function getSavedPages() { var pages = {}; c = getFromStorage('pages'); console.log('c=' + c); if (null !== c && typeof c !== 'undefined') { pages = JSON.parse(c); } console.log('pages=' + JSON.stringify(pages)); return pages; } function updateVisitedPages() { var url = window.location.href; console.log('updateVisitedPages:url=' + url); /* * If we load the product page and if the product page already has the contextpages code, it will save this fake page as * a 'visited page' storage, then it process/show it to the admin as he browses his own site's product pages. * Solutions: * 1) prevent cookies from being set. This is likely too harsh because the embedded page may rely on cookies being set * for proper display (even if not for full functioning). This doesnt work when we use local storage instead of cookies. * Besides, the 2 solutions I found for it don't work: * a) iframe sandbox: * https://stackoverflow.com/questions/44837450/recommended-method-to-prevent-any-content-inside-iframe-from-setting-cookies . * With empty sandbox, nothing loads, and main frame cannot access iframe contents. * With sandbox="allow-same-origin allow-scripts", it doesnt prevent cookies from being written. * b) data-ref instead of src, and data-cookiescript: https://cookie-script.com/how-to-block-third-party-cookies.html . Unfortunately doesnt work. * 2) overwrite our own javascript function that sets the storage. Use poisonSetStorageFunction * 3) detect that we're in this "contextpages embeds self" mode and dont set storage * 4) have "save storage" visits.js refuse to write storage for pages created by reflect.php */ if( /reflect-dumps\/[0-9]+\.html$/.test(url) ) { console.log('Reflect dump, avoiding save'); return; } var thumb, title, price; [thumb, title, detail, price] = getPicTitleContentPrice(ContextFeatures, document); if( (null === thumb) || (null === title) ) { console.log('Could not find thumb or title, avoiding save'); return; } var pages = getSavedPages(); /* HUGE time & complexity savings: avoid having to retrieve url later from the server, just save the elements we need. * * Initially used URL as a key. However, multiple URLs can point to the same product (variablility in parameters - including * parameters we're introducing for promo). Thus, multiple urls were displaying the same thumbs/title/price. To fix this, * changed key to be the combination we're showing (except detail, which is a long form/paragraph). * * Would have liked to have a key be a dictionary itself, but "dictionary as key" that doesnt work. * I tried key as a tuple. However when serializing/deserializing that in JSON, it is read back as a string instead of tuple * Third attempt is to use an explicit separator. * * The browser makes .src into an absolute path. */ var key = thumb.src + ContextKeySeparator + title.textContent + ContextKeySeparator; if( null != price ) { key += price.textContent; } currentPageRecord = pages[key]; console.log('updateVisitedPages:currentPageRecord=' + JSON.stringify(currentPageRecord)); if (typeof currentPageRecord == 'undefined') { pages[key] = {} pages[key]['hitcount'] = 1 pages[key]['url'] = url; } else { pages[key]['hitcount'] += 1 if( url.length < pages[key]['url'] ) { // save the simplest/shortest path pages[key]['url'] = url; } } pages[key]['accessed'] = now(); if( detail ) { pages[key]['detail'] = detail.innerHTML; } setToStorage('pages', JSON.stringify(pages)); return pages; } function getPagesArraySortedByHitcount(pages) { // Create sortable items array from dictionary. var sortable = []; for (var k in pages) { /* k is actually a key */ // push({k:pages[k]}) doesnt work, uses 'k' as a literal as opposed to its value dict = {} dict[k] = pages[k] sortable.push(dict); } // Sort the array based on the second element sortable.sort(function(a, b) { ka = Object.keys(a)[0] // There is only one key oa = a[ka] kb = Object.keys(b)[0] // There is only one key ob = b[kb] return ob['hitcount'] - oa['hitcount']; }); return sortable; } function getPicTitleContentPrice(f, pagedoc) { thumb = getElementsByXPath(f.xpath_thumb_pic, pagedoc); if( thumb.length != 1 ) { console.log('thumb.length=' + thumb.length); return [null, null, null, null]; } else { thumb = thumb[0].cloneNode(true); } title = getElementsByXPath(f.xpath_thumb_title, pagedoc); if( title.length != 1 ) { console.log('title.length=' + title.length); return [thumb, null, null, null]; } else { title = title[0].cloneNode(true); } /* From here down, optionals. */ if( f.xpath_thumb_detail ) { detail = getElementsByXPath(f.xpath_thumb_detail, pagedoc); if( detail.length != 1 ) { console.log('detail.length=' + detail.length); detail = null; } else { detail = detail[0].cloneNode(true); } } else { detail = null; } if( f.xpath_thumb_price ) { price = getElementsByXPath(f.xpath_thumb_price, pagedoc); if( price.length != 1 ) { console.log('price.length=' + price.length); price = null; } else { price = price[0].cloneNode(true); } } else { price = null; } return [thumb, title, detail, price]; } function loadedContextContent(f, thisPageRecord) { // Don't suggest currently displayed page var topKey = Object.keys(thisPageRecord)[0]; var url = thisPageRecord[topKey]['url']; var hitcount = thisPageRecord[topKey]['hitcount']; if( url == window.location.href) { /* this is a first check, there is a second below */ return; } var onSaleMinCount = f.onsale_visit_min_count; if( f.feature_enabled_auto_sale && onSaleMinCount > 0 ) { if( hitcount >= onSaleMinCount ) { txtRibbon = f.onsale_ribbon_text; if( f.onsale_var_name ) { url = updateQueryStringParameter(url, f.onsale_var_name, f.onsale_var_value) if( url == window.location.href) { /* this is a second check, to avoid showing a thumb for the current "on sale" url (we saved this url without the on-sale params) */ return false; } } } else { txtRibbon = ''; // no ribbon } } else { txtRibbon = ''; } var shown = false; var tupleKeys = topKey.split(ContextKeySeparator); txtDetail = thisPageRecord[topKey]['detail']; if( 'undefined' === typeof(txtDetail) || 0 == txtDetail.length ) { txtDetail = null; } txtPrice = tupleKeys[2]; if( 'undefined' === (typeof txtPrice) || 0 == txtPrice.length ) { txtPrice = null; } var urlThumb = tupleKeys[0]; var txtTitle = tupleKeys[1]; console.log('features enabled:featured_text=' + f.feature_enabled_featured_text + ' popup=' + f.feature_enabled_popup + ' banner_horizontal_vertical=' + f.feature_enabled_banner_horizontal_vertical + ' auto_sale=' + f.feature_enabled_auto_sale); console.log('xpath_display_container='+f.xpath_display_container + ' txtTitle=' + txtTitle + ' txtPrice=' + txtPrice + ' txtRibbon=' + txtRibbon + ' txtDetail='+txtDetail); if( f.feature_enabled_featured_text && f.xpath_display_container && f.xpath_display_container.length && f.xpath_thumb_detail && f.xpath_thumb_detail.length ) { shown = displayFeaturedText(f, urlThumb = urlThumb, txtTitle = txtTitle, txtDetail = txtDetail, txtPrice = txtPrice, href = url, txtRibbon); } else if( f.feature_enabled_popup || f.feature_enabled_banner_horizontal_vertical ) { shown = displayInBanner(f, urlThumb = urlThumb, txtTitle = txtTitle, txtPrice = txtPrice, href = url, txtRibbon); } if( shown ) { ContextThumbsShown += 1; ContextDisplay.style.visibility = 'visible'; return true; } return false; } function loadThumbsFromStorage(f, pagesSortedArrayOfDictEntries, maxShow) { var i = 0; while( i < pagesSortedArrayOfDictEntries.length && ContextThumbsShown < maxShow ) { loadedContextContent(f, pagesSortedArrayOfDictEntries[i]); i++; } } // Signal this is a top page, to avoid infinite recursive inclusions. // Have to use "var", because const, let, and class don't create properties on the global object. var ContextThisPage = "main"; var ContextThumbsShown = 0; var ContextDisplay = null; const ContextKeySeparator = '@#@#'; function processFeature(f) { updateVisitedPages(); if( 0 == f.show_visited_count ) { console.log('ContextPages: feature show_visited is not enabled, exiting early'); return; } if( ! createDisplay(f) ) { return; } var pagesDict = getSavedPages(); var pagesSortedArrayOfDictEntries = getPagesArraySortedByHitcount(pagesDict); var maxShow = f.feature_enabled_featured_text ? 1 : f.show_visited_count; // show maximum one if "featured text" is enabled loadThumbsFromStorage(f, pagesSortedArrayOfDictEntries, maxShow); } function logAccess(autoid) { var xhttp; if (window.XMLHttpRequest) { // modern browsers xhttp = new XMLHttpRequest(); } else { // old IE browsers xhttp = new ActiveXObject("Microsoft.XMLHTTP"); } var url = 'https://my.contextpages.com/log-access.php'; var params = 'ContextTopUrl=' + window.top.location.href + '&ContextLastClicked=' + getCookie('ContextLastClicked') + '&id=' + autoid; xhttp.open("POST", url, true); xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhttp.send(params); // This is on its way to be logged on the server. Delete to prepare for the next click. eraseCookie('ContextLastClicked'); } function ContextPagesOnLoad() { console.log('ContextPagesOnLoad'); // Before anything, check if this is a page we/ourselves included in a hidden iframe or div, // to extract thumb pics/title/price etc. This avoids an infinite recursion of calling hidden iframes etc. // https://stackoverflow.com/questions/935127/how-to-access-parent-iframe-from-javascript // https://stackoverflow.com/questions/7647042/sharing-global-javascript-variable-of-a-page-with-an-iframe-within-that-page if( window.top != window && 'main' == window.top.ContextThisPage ) { // we should recurse, in case there are iframes-in-iframes-in-iframes ... console.log('This is an included page, returning early'); return; } logAccess(ContextFeatures["autoid"]); if(typeof ContextFeaturesOverride !== 'undefined') { ContextFeatures = ContextFeaturesOverride; console.log('ContextPages: Overriding ContextFeatures='+JSON.stringify(ContextFeatures)); } else { console.log('ContextPages: ContextFeatures' + JSON.stringify(ContextFeatures)); } if( isDictEmpty(ContextFeatures) ) { console.log('ContextPages: ContextFeatures is empty, exiting early'); return; } var cssLocation = 'https://my.contextpages.com/assets/css/contextpages.css'; addCSSHref(cssLocation); processFeature(ContextFeatures); } window.addEventListener("load",function(event) { ContextPagesOnLoad(); },false);