diff iframe-resizer/src/iframeResizer.contentWindow.js @ 0:ac5f9272033b draft

first upload
author saskia-hiltemann
date Tue, 01 Jul 2014 11:42:23 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iframe-resizer/src/iframeResizer.contentWindow.js	Tue Jul 01 11:42:23 2014 -0400
@@ -0,0 +1,564 @@
+/*
+ * 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      = 64,
+		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');
+				},
+				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;
+			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);
+		}
+
+		if (isMessageForUs()){
+			if (firstRun){ //Check msg ID
+				initFromParent();
+			} else if ('reset' === getMessageType()){
+				resetFromParent();
+			} else if (event.data !== initMsg && !isMiddleTier()){
+				warn('Unexpected message ('+event.data+')');
+			}
+		}
+	}
+
+	addEventListener(window, 'message', receiver);
+
+})();