text: /*
   * File: iframeSizer.contentWindow.js
   * Desc: Include this file in any page being loaded into an iframe
   *       to force the iframe to resize to the content size.
   * Requires: iframeResizer.js on host page.
   * Author: David J. Bradshaw - dave@bradshaw.net
   * Contributor: Jure Mav - jure.mav@gmail.com
   */
  
  ;(function() {
  	'use strict';
  
  	var
  		autoResize            = true,
  		base                  = 10,
  		bodyBackground        = '',
  		bodyMargin            = 0,
  		bodyMarginStr         = '',
  		bodyPadding           = '',
  		calculateWidth        = false,
  		doubleEventList       = {'resize':1,'click':1},
  		eventCancelTimer      = 128,
  		height                = 1,
  		firstRun              = true,
  		heightCalcModeDefault = 'offset',
  		heightCalcMode        = heightCalcModeDefault,
  		initLock              = true,
  		initMsg               = '',
  		interval              = 32,
  		logging               = false,
  		msgID                 = '[iFrameSizer]',  //Must match host page msg ID
  		msgIdLen              = msgID.length,
  		myID                  = '',
  		publicMethods         = false,
  		resetRequiredMethods  = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1},
  		targetOriginDefault   = '*',
  		target                = window.parent,
  		tolerance             = 0,
  		triggerLocked         = false,
  		triggerLockedTimer    = null,
  		width                 = 1;
  
  
  	function addEventListener(el,evt,func){
  		if ('addEventListener' in window){
  			el.addEventListener(evt,func, false);
  		} else if ('attachEvent' in window){ //IE
  			el.attachEvent('on'+evt,func);
  		}
  	}
  
  	function formatLogMsg(msg){
  		return msgID + '[' + myID + ']' + ' ' + msg;
  	}
  
  	function log(msg){
  		if (logging && ('object' === typeof window.console)){
  			console.log(formatLogMsg(msg));
  		}
  	}
  
  	function warn(msg){
  		if ('object' === typeof window.console){
  			console.warn(formatLogMsg(msg));
  		}
  	}
  
  
  	function init(){
  		log('Initialising iFrame');
  		readData();
  		setMargin();
  		setBodyStyle('background',bodyBackground);
  		setBodyStyle('padding',bodyPadding);
  		injectClearFixIntoBodyElement();
  		checkHeightMode();
  		stopInfiniteResizingOfIFrame();
  		setupPublicMethods();
  		startEventListeners();
  		sendSize('init','Init message from host page');
  	}
  
  	function readData(){
  
  		var data = initMsg.substr(msgIdLen).split(':');
  
  		function strBool(str){
  			return 'true' === str ? true : false;
  		}
  
  		myID             = data[0];
  		bodyMargin       = (undefined !== data[1]) ? Number(data[1])   : bodyMargin; //For V1 compatibility
  		calculateWidth   = (undefined !== data[2]) ? strBool(data[2])  : calculateWidth;
  		logging          = (undefined !== data[3]) ? strBool(data[3])  : logging;
  		interval         = (undefined !== data[4]) ? Number(data[4])   : interval;
  		publicMethods    = (undefined !== data[5]) ? strBool(data[5])  : publicMethods;
  		autoResize       = (undefined !== data[6]) ? strBool(data[6])  : autoResize;
  		bodyMarginStr    = data[7];
  		heightCalcMode   = (undefined !== data[8]) ? data[8]           : heightCalcMode;
  		bodyBackground   = data[9];
  		bodyPadding      = data[10];
  		tolerance        = (undefined !== data[11]) ? Number(data[11]) : tolerance;
  	}
  
  	function chkCSS(attr,value){
  		if (-1 !== value.indexOf('-')){
  			warn('Negative CSS value ignored for '+attr);
  			value='';
  		}
  		return value;
  	}
  
  	function setBodyStyle(attr,value){
  		if ((undefined !== value) && ('' !== value) && ('null' !== value)){
  			document.body.style[attr] = value;
  			log('Body '+attr+' set to "'+value+'"');
  		}
  	}
  
  	function setMargin(){
  		//If called via V1 script, convert bodyMargin from int to str 
  		if (undefined === bodyMarginStr){
  			bodyMarginStr = bodyMargin+'px';
  		}
  		chkCSS('margin',bodyMarginStr);
  		setBodyStyle('margin',bodyMarginStr);
  	}
  
  	function stopInfiniteResizingOfIFrame(){
  		document.documentElement.style.height = '';
  		document.body.style.height = '';
  		log('HTML & body height set to "auto"');
  	}
  
  	function initWindowResizeListener(){
  		addEventListener(window,'resize', function(){
  			sendSize('resize','Window resized');
  		});
  	}
  
  	function initWindowClickListener(){
  		addEventListener(window,'click', function(){
  			sendSize('click','Window clicked');
  		});
  	}
  
  	function checkHeightMode(){
  		if (heightCalcModeDefault !== heightCalcMode){
  			if (!(heightCalcMode in getHeight)){
  				warn(heightCalcMode + ' is not a valid option for heightCalculationMethod.');
  				heightCalcMode='bodyScroll';
  			}
  			log('Height calculation method set to "'+heightCalcMode+'"');
  		}
  	}
  
  	function startEventListeners(){
  		if ( true === autoResize ) {
  			initWindowResizeListener();
  			initWindowClickListener();
  			setupMutationObserver();
  		}
  		else {
  			log('Auto Resize disabled');
  		}
  	}
  
  	function injectClearFixIntoBodyElement(){
  		var clearFix = document.createElement('div');
  		clearFix.style.clear = 'both';
  		clearFix.style.display = 'block'; //Guard against this having been globally redefined in CSS.
  		document.body.appendChild(clearFix);
  	}
  
  	function setupPublicMethods(){
  		if (publicMethods) {
  			log('Enable public methods');
  
  			window.parentIFrame = {
  				close: function closeF(){
  					sendSize('close','parentIFrame.close()', 0, 0);
  				},
  				getId: function getIdF(){
  					return myID;
  				},
  				reset: function resetF(){
  					resetIFrame('parentIFrame.size');
  				},
  				scrollTo: function scrollToF(x,y){
  					sendMsg(x,y,'scrollTo');
  				},
  				sendMessage: function sendMessageF(msg,targetOrigin){
  					sendMsg(0,0,'message',msg,targetOrigin);
  				},
  				setHeightCalculationMethod: function setHeightCalculationMethodF(heightCalculationMethod){
  					heightCalcMode = heightCalculationMethod;
  					checkHeightMode();
  				},
  				setTargetOrigin: function setTargetOriginF(targetOrigin){
  					log('Set targetOrigin: '+targetOrigin);
  					targetOriginDefault = targetOrigin;
  				},
  				size: function sizeF(customHeight, customWidth){
  					var valString = ''+(customHeight?customHeight:'')+(customWidth?','+customWidth:'');
  					lockTrigger();
  					sendSize('size','parentIFrame.size('+valString+')', customHeight, customWidth);
  				}
  			};
  		}
  	}
  
  	function initInterval(){
  		if ( 0 !== interval ){
  			log('setInterval: '+interval+'ms');
  			setInterval(function(){
  				sendSize('interval','setInterval: '+interval);
  			},Math.abs(interval));
  		}
  	}
  
  	function setupInjectElementLoadListners(mutations){
  		function addLoadListener(element){
  			if (element.height === undefined || element.width === undefined || 0 === element.height || 0 === element.width){
  				log('Attach listerner to '+element.src);
  				addEventListener(element,'load', function imageLoaded(){
  					sendSize('imageLoad','Image loaded');
  				});
  			}
  		}
  
  		mutations.forEach(function (mutation) {
  			if (mutation.type === 'attributes' && mutation.attributeName === 'src'){
  				addLoadListener(mutation.target);
  			} else if (mutation.type === 'childList'){
  				var images = mutation.target.querySelectorAll('img');
  				Array.prototype.forEach.call(images,function (image) {
  					addLoadListener(image);
  				});
  			}
  		});
  	}
  
  	function setupMutationObserver(){
  
  		var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  
  		function createMutationObserver(){
  			var
  				target = document.querySelector('body'),
  
  				config = {
  					attributes            : true,
  					attributeOldValue     : false,
  					characterData         : true,
  					characterDataOldValue : false,
  					childList             : true,
  					subtree               : true
  				},
  
  				observer = new MutationObserver(function(mutations) {
  					sendSize('mutationObserver','mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type);
  					setupInjectElementLoadListners(mutations); //Deal with WebKit asyncing image loading when tags are injected into the page
  				});
  
  			log('Enable MutationObserver');
  			observer.observe(target, config);
  		}
  
  		if (MutationObserver){
  			if (0 > interval) {
  				initInterval();
  			} else {
  				createMutationObserver();
  			}
  		}
  		else {
  			warn('MutationObserver not supported in this browser!');
  			initInterval();
  		}
  	}
  
  
  	// document.documentElement.offsetHeight is not reliable, so
  	// we have to jump through hoops to get a better value.
  	function getBodyOffsetHeight(){
  		function getComputedBodyStyle(prop) {
  			function convertUnitsToPxForIE8(value) {
  				var PIXEL = /^\d+(px)?$/i;
  
  				if (PIXEL.test(value)) {
  					return parseInt(value,base);
  				}
  
  				var
  					style = el.style.left,
  					runtimeStyle = el.runtimeStyle.left;
  
  				el.runtimeStyle.left = el.currentStyle.left;
  				el.style.left = value || 0;
  				value = el.style.pixelLeft;
  				el.style.left = style;
  				el.runtimeStyle.left = runtimeStyle;
  
  				return value;
  			}
  
  			var
  				el = document.body,
  				retVal = 0;
  
  			if (('defaultView' in document) && ('getComputedStyle' in document.defaultView)) {
  				retVal = document.defaultView.getComputedStyle(el, null);
  				retVal = (null !== retVal) ? retVal[prop] : 0;
  			} else {//IE8
  				retVal =  convertUnitsToPxForIE8(el.currentStyle[prop]);
  			}
  
  			return parseInt(retVal,base);
  		}
  
  		return  document.body.offsetHeight +
  				getComputedBodyStyle('marginTop') +
  				getComputedBodyStyle('marginBottom');
  	}
  
  	function getBodyScrollHeight(){
  		return document.body.scrollHeight;
  	}
  
  	function getDEOffsetHeight(){
  		return document.documentElement.offsetHeight;
  	}
  
  	function getDEScrollHeight(){
  		return document.documentElement.scrollHeight;
  	}
  
  	//From https://github.com/guardian/iframe-messenger
  	function getLowestElementHeight() {
  		var
  			allElements       = document.querySelectorAll('body *'),
  			allElementsLength = allElements.length,
  			maxBottomVal      = 0,
  			timer             = new Date().getTime();
  
  		for (var i = 0; i < allElementsLength; i++) {
  			if (allElements[i].getBoundingClientRect().bottom > maxBottomVal) {
  				maxBottomVal = allElements[i].getBoundingClientRect().bottom;
  			}
  		}
  
  		timer = new Date().getTime() - timer;
  
  		log('Parsed '+allElementsLength+' HTML elements');
  		log('LowestElement bottom position calculated in ' + timer + 'ms');
  
  		return maxBottomVal;
  	}
  
  	function getAllHeights(){
  		return [
  			getBodyOffsetHeight(),
  			getBodyScrollHeight(),
  			getDEOffsetHeight(),
  			getDEScrollHeight()
  		];
  	}
  
  	function getMaxHeight(){
  		return Math.max.apply(null,getAllHeights());
  	}
  
  	function getMinHeight(){
  		return Math.min.apply(null,getAllHeights());
  	}
  
  	function getBestHeight(){
  		return Math.max(getBodyOffsetHeight(),getLowestElementHeight());
  	}
  
  	var getHeight = {
  		offset                : getBodyOffsetHeight, //Backward compatability
  		bodyOffset            : getBodyOffsetHeight,
  		bodyScroll            : getBodyScrollHeight,
  		documentElementOffset : getDEOffsetHeight,
  		scroll                : getDEScrollHeight, //Backward compatability
  		documentElementScroll : getDEScrollHeight,
  		max                   : getMaxHeight,
  		min                   : getMinHeight,
  		grow                  : getMaxHeight,
  		lowestElement         : getBestHeight
  	};
  
  	function getWidth(){
  		return Math.max(
  			document.documentElement.scrollWidth,
  			document.body.scrollWidth
  		);
  	}
  
  	function sendSize(triggerEvent, triggerEventDesc, customHeight, customWidth){
  
  		var	currentHeight,currentWidth;
  
  		function recordTrigger(){
  			if (!(triggerEvent in {'reset':1,'resetPage':1,'init':1})){
  				log( 'Trigger event: ' + triggerEventDesc );
  			}
  		}
  
  		function resizeIFrame(){
  			height = currentHeight;
  			width  = currentWidth;
  
  			sendMsg(height,width,triggerEvent);
  		}
  
  		function isDoubleFiredEvent(){
  			return  triggerLocked && (triggerEvent in doubleEventList);
  		}
  
  		function isSizeChangeDetected(){
  			function checkTolarance(a,b){
  				var retVal = Math.abs(a-b) <= tolerance;
  				return !retVal;
  			}
  
  			currentHeight = (undefined !== customHeight)  ? customHeight : getHeight[heightCalcMode]();
  			currentWidth  = (undefined !== customWidth )  ? customWidth  : getWidth();
  
  			return	checkTolarance(height,currentHeight) ||
  					(calculateWidth && checkTolarance(width,currentWidth));
  
  			//return	(height !== currentHeight) ||
  			//		(calculateWidth && width !== currentWidth);
  		}
  
  		function isForceResizableEvent(){
  			return !(triggerEvent in {'init':1,'interval':1,'size':1});
  		}
  
  		function isForceResizableHeightCalcMode(){
  			return (heightCalcMode in resetRequiredMethods);
  		}
  
  		function logIgnored(){
  			log('No change in size detected');
  		}
  
  		function checkDownSizing(){
  			if (isForceResizableEvent() && isForceResizableHeightCalcMode()){
  				resetIFrame(triggerEventDesc);
  			} else if (!(triggerEvent in {'interval':1})){
  				recordTrigger();
  				logIgnored();
  			}
  		}
  
  		if (!isDoubleFiredEvent()){
  			if (isSizeChangeDetected()){
  				recordTrigger();
  				lockTrigger();
  				resizeIFrame();
  			} else {
  				checkDownSizing();
  			}
  		} else {
  			log('Trigger event cancelled: '+triggerEvent);
  		}
  	}
  
  	function lockTrigger(){
  		if (!triggerLocked){
  			triggerLocked = true;
  			log('Trigger event lock on');
  		}
  		clearTimeout(triggerLockedTimer);
  		triggerLockedTimer = setTimeout(function(){
  			triggerLocked = false;
  			log('Trigger event lock off');
  			log('--');
  		},eventCancelTimer);
  	}
  
  	function triggerReset(triggerEvent){
  		height = getHeight[heightCalcMode]();
  		width  = getWidth();
  
  		sendMsg(height,width,triggerEvent);
  	}
  
  	function resetIFrame(triggerEventDesc){
  		var hcm = heightCalcMode;
  		heightCalcMode = heightCalcModeDefault;
  
  		log('Reset trigger event: ' + triggerEventDesc);
  		lockTrigger();
  		triggerReset('reset');
  
  		heightCalcMode = hcm;
  	}
  
  	function sendMsg(height,width,triggerEvent,msg,targetOrigin){
  		function setTargetOrigin(){
  			if (undefined === targetOrigin){
  				targetOrigin = targetOriginDefault;
  			} else {
  				log('Message targetOrigin: '+targetOrigin);
  			}
  		}
  
  		function sendToParent(){
  			var
  				size  = height + ':' + width,
  				message = myID + ':' +  size + ':' + triggerEvent + (undefined !== msg ? ':' + msg : '');
  
  			log('Sending message to host page (' + message + ')');
  			target.postMessage( msgID + message, targetOrigin);
  		}
  
  		setTargetOrigin();
  		sendToParent();
  	}
  
  	function receiver(event) {
  		function isMessageForUs(){
  			return msgID === (''+event.data).substr(0,msgIdLen); //''+ Protects against non-string messages
  		}
  
  		function initFromParent(){
  			initMsg = event.data;
  			target  = event.source;
  
  			init();
  			firstRun = false;
  			setTimeout(function(){ initLock = false;},eventCancelTimer);
  		}
  
  		function resetFromParent(){
  			if (!initLock){
  				log('Page size reset by host page');
  				triggerReset('resetPage');
  			} else {
  				log('Page reset ignored by init');
  			}
  		}
  
  		function getMessageType(){
  			return event.data.split(']')[1];
  		}
  
  		function isMiddleTier(){
  			return ('iFrameResize' in window);
  		}
  
  		function isInitMsg(){
  			//test if this message is from a child below us. This is an ugly test, however, updating
  			//the message format would break backwards compatibity.
  			return event.data.split(':')[2] in {'true':1,'false':1};
  		}
  
  		if (isMessageForUs()){
  			if (firstRun && isInitMsg()){ //Check msg ID
  				initFromParent();
  			} else if ('reset' === getMessageType()){
  				resetFromParent();
  			} else if (event.data !== initMsg && !isMiddleTier()){
  				warn('Unexpected message ('+event.data+')');
  			}
  		}
  	}
  
  	addEventListener(window, 'message', receiver);
  
  })();
  