diff --git a/examples/assets/library/iframe-content.js b/examples/assets/library/iframe-content.js index 020b317f1..6b991ff0b 100644 --- a/examples/assets/library/iframe-content.js +++ b/examples/assets/library/iframe-content.js @@ -3,101 +3,195 @@ * 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. + * Doc: https://github.com/davidjbradshaw/iframe-resizer * Author: David J. Bradshaw - dave@bradshaw.net * Contributor: Jure Mav - jure.mav@gmail.com * Contributor: Ian Caunce - ian@hallnet.co.uk */ -;(function() { + +;(function(undefined) { 'use strict'; + if(typeof window === 'undefined') return; // don't run for server side render + var autoResize = true, base = 10, bodyBackground = '', bodyMargin = 0, bodyMarginStr = '', + bodyObserver = null, bodyPadding = '', calculateWidth = false, doubleEventList = {'resize':1,'click':1}, eventCancelTimer = 128, - height = 1, firstRun = true, - heightCalcModeDefault = 'offset', + height = 1, + heightCalcModeDefault = 'bodyOffset', heightCalcMode = heightCalcModeDefault, initLock = true, initMsg = '', inPageLinks = {}, interval = 32, + intervalTimer = null, 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}, - resizeFrom = 'parent', - targetOriginDefault = '*', + observer = null, + resetRequiredMethods = {max:1,min:1,bodyScroll:1,documentElementScroll:1}, + resizeFrom = 'child', + sendPermit = true, target = window.parent, + targetOriginDefault = '*', tolerance = 0, triggerLocked = false, triggerLockedTimer = null, - width = 1; + throttledTimer = 16, + width = 1, + widthCalcModeDefault = 'scroll', + widthCalcMode = widthCalcModeDefault, + win = window, + messageCallback = function() { warn('MessageCallback function not defined'); }, + readyCallback = function() {}, + pageInfoCallback = function() {}, + customCalcMethods = { + height: function() { + warn('Custom height calculation function not defined'); + return document.documentElement.offsetHeight; + }, + width: function() { + warn('Custom width calculation function not defined'); + return document.body.scrollWidth; + } + }, + eventHandlersByName = {}; - function addEventListener(el,evt,func){ - if ('addEventListener' in window){ + function addEventListener(el,evt,func) { + /* istanbul ignore else */ // Not testable in phantonJS + if ('addEventListener' in window) { el.addEventListener(evt,func, false); - } else if ('attachEvent' in window){ //IE + } else if ('attachEvent' in window) { //IE el.attachEvent('on'+evt,func); } } - function formatLogMsg(msg){ + function removeEventListener(el,evt,func) { + /* istanbul ignore else */ // Not testable in phantonJS + if ('removeEventListener' in window) { + el.removeEventListener(evt,func, false); + } else if ('detachEvent' in window) { //IE + el.detachEvent('on'+evt,func); + } + } + + function capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + } + + //Based on underscore.js + function throttle(func) { + var + context, args, result, + timeout = null, + previous = 0, + later = function() { + previous = getNow(); + timeout = null; + result = func.apply(context, args); + if (!timeout) { + context = args = null; + } + }; + + return function() { + var now = getNow(); + + if (!previous) { + previous = now; + } + + var remaining = throttledTimer - (now - previous); + + context = this; + args = arguments; + + if (remaining <= 0 || remaining > throttledTimer) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + + previous = now; + result = func.apply(context, args); + + if (!timeout) { + context = args = null; + } + + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + + return result; + }; + } + + var getNow = Date.now || function() { + /* istanbul ignore next */ // Not testable in PhantonJS + return new Date().getTime(); + }; + + function formatLogMsg(msg) { return msgID + '[' + myID + ']' + ' ' + msg; } - function log(msg){ - if (logging && ('object' === typeof window.console)){ + function log(msg) { + if (logging && ('object' === typeof window.console)) { console.log(formatLogMsg(msg)); } } - function warn(msg){ - if ('object' === typeof window.console){ + function warn(msg) { + if ('object' === typeof window.console) { console.warn(formatLogMsg(msg)); } } - function init(){ - log('Initializing iFrame'); - readData(); + function init() { + readDataFromParent(); + log('Initialising iFrame ('+location.href+')'); + readDataFromPage(); setMargin(); setBodyStyle('background',bodyBackground); setBodyStyle('padding',bodyPadding); injectClearFixIntoBodyElement(); checkHeightMode(); + checkWidthMode(); stopInfiniteResizingOfIFrame(); setupPublicMethods(); startEventListeners(); inPageLinks = setupInPageLinks(); sendSize('init','Init message from host page'); + readyCallback(); } - function readData(){ - - var data = initMsg.substr(msgIdLen).split(':'); + function readDataFromParent() { - function strBool(str){ + function strBool(str) { return 'true' === str ? true : false; } + var data = initMsg.substr(msgIdLen).split(':'); + 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; @@ -105,84 +199,148 @@ bodyPadding = data[10]; tolerance = (undefined !== data[11]) ? Number(data[11]) : tolerance; inPageLinks.enable = (undefined !== data[12]) ? strBool(data[12]): false; - resizeFrom = data[13]; + resizeFrom = (undefined !== data[13]) ? data[13] : resizeFrom; + widthCalcMode = (undefined !== data[14]) ? data[14] : widthCalcMode; + } + + function readDataFromPage() { + function readData() { + var data = window.iFrameResizer; + + log('Reading data from page: ' + JSON.stringify(data)); + + messageCallback = ('messageCallback' in data) ? data.messageCallback : messageCallback; + readyCallback = ('readyCallback' in data) ? data.readyCallback : readyCallback; + targetOriginDefault = ('targetOrigin' in data) ? data.targetOrigin : targetOriginDefault; + heightCalcMode = ('heightCalculationMethod' in data) ? data.heightCalculationMethod : heightCalcMode; + widthCalcMode = ('widthCalculationMethod' in data) ? data.widthCalculationMethod : widthCalcMode; + } + + function setupCustomCalcMethods(calcMode, calcFunc) { + if ('function' === typeof calcMode) { + log('Setup custom ' + calcFunc + 'CalcMethod'); + customCalcMethods[calcFunc] = calcMode; + calcMode = 'custom'; + } + + return calcMode; + } + + if(('iFrameResizer' in window) && (Object === window.iFrameResizer.constructor)) { + readData(); + heightCalcMode = setupCustomCalcMethods(heightCalcMode, 'height'); + widthCalcMode = setupCustomCalcMethods(widthCalcMode, 'width'); + } + + log('TargetOrigin for parent set to: ' + targetOriginDefault); } - function chkCSS(attr,value){ - if (-1 !== value.indexOf('-')){ + + 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)){ + function setBodyStyle(attr,value) { + if ((undefined !== value) && ('' !== value) && ('null' !== value)) { document.body.style[attr] = value; log('Body '+attr+' set to "'+value+'"'); } } - function setMargin(){ + function setMargin() { //If called via V1 script, convert bodyMargin from int to str - if (undefined === bodyMarginStr){ + if (undefined === bodyMarginStr) { bodyMarginStr = bodyMargin+'px'; } - chkCSS('margin',bodyMarginStr); - setBodyStyle('margin',bodyMarginStr); + + setBodyStyle('margin',chkCSS('margin',bodyMarginStr)); } - function stopInfiniteResizingOfIFrame(){ + function stopInfiniteResizingOfIFrame() { document.documentElement.style.height = ''; document.body.style.height = ''; log('HTML & body height set to "auto"'); } - function addTriggerEvent(options){ - function addListener(eventName){ - addEventListener(window,eventName,function(e){ - sendSize(options.eventName,options.eventType); - }); - } + function manageTriggerEvent(options) { + + var listener = { + add: function(eventName) { + function handleEvent() { + sendSize(options.eventName,options.eventType); + } + + eventHandlersByName[eventName] = handleEvent; - if(options.eventNames && Array.prototype.map){ + addEventListener(window,eventName,handleEvent); + }, + remove: function(eventName) { + var handleEvent = eventHandlersByName[eventName]; + delete eventHandlersByName[eventName]; + + removeEventListener(window,eventName,handleEvent); + } + }; + + if(options.eventNames && Array.prototype.map) { options.eventName = options.eventNames[0]; - options.eventNames.map(addListener); + options.eventNames.map(listener[options.method]); } else { - addListener(options.eventName); + listener[options.method](options.eventName); } - log('Added event listener: ' + options.eventType); + log(capitalizeFirstLetter(options.method) + ' event listener: ' + options.eventType); } - function initEventListeners(){ - addTriggerEvent({ eventType: 'Animation Start', eventNames: ['animationstart','webkitAnimationStart'] }); - addTriggerEvent({ eventType: 'Animation Iteration', eventNames: ['animationiteration','webkitAnimationIteration'] }); - addTriggerEvent({ eventType: 'Animation End', eventNames: ['animationend','webkitAnimationEnd'] }); - addTriggerEvent({ eventType: 'Device Orientation Change', eventName: 'deviceorientation' }); - addTriggerEvent({ eventType: 'Transition End', eventNames: ['transitionend','webkitTransitionEnd','MSTransitionEnd','oTransitionEnd','otransitionend'] }); - addTriggerEvent({ eventType: 'Window Clicked', eventName: 'click' }); - //addTriggerEvent({ eventType: 'Window Mouse Down', eventName: 'mousedown' }); - //addTriggerEvent({ eventType: 'Window Mouse Up', eventName: 'mouseup' }); - if('child' === resizeFrom){ - addTriggerEvent({ eventType: 'IFrame Resized', eventName: 'resize' }); + function manageEventListeners(method) { + manageTriggerEvent({method:method, eventType: 'Animation Start', eventNames: ['animationstart','webkitAnimationStart'] }); + manageTriggerEvent({method:method, eventType: 'Animation Iteration', eventNames: ['animationiteration','webkitAnimationIteration'] }); + manageTriggerEvent({method:method, eventType: 'Animation End', eventNames: ['animationend','webkitAnimationEnd'] }); + manageTriggerEvent({method:method, eventType: 'Input', eventName: 'input' }); + manageTriggerEvent({method:method, eventType: 'Mouse Up', eventName: 'mouseup' }); + manageTriggerEvent({method:method, eventType: 'Mouse Down', eventName: 'mousedown' }); + manageTriggerEvent({method:method, eventType: 'Orientation Change', eventName: 'orientationchange' }); + manageTriggerEvent({method:method, eventType: 'Print', eventName: ['afterprint', 'beforeprint'] }); + manageTriggerEvent({method:method, eventType: 'Ready State Change', eventName: 'readystatechange' }); + manageTriggerEvent({method:method, eventType: 'Touch Start', eventName: 'touchstart' }); + manageTriggerEvent({method:method, eventType: 'Touch End', eventName: 'touchend' }); + manageTriggerEvent({method:method, eventType: 'Touch Cancel', eventName: 'touchcancel' }); + manageTriggerEvent({method:method, eventType: 'Transition Start', eventNames: ['transitionstart','webkitTransitionStart','MSTransitionStart','oTransitionStart','otransitionstart'] }); + manageTriggerEvent({method:method, eventType: 'Transition Iteration', eventNames: ['transitioniteration','webkitTransitionIteration','MSTransitionIteration','oTransitionIteration','otransitioniteration'] }); + manageTriggerEvent({method:method, eventType: 'Transition End', eventNames: ['transitionend','webkitTransitionEnd','MSTransitionEnd','oTransitionEnd','otransitionend'] }); + if('child' === resizeFrom) { + manageTriggerEvent({method:method, eventType: 'IFrame Resized', eventName: 'resize' }); } } - function checkHeightMode(){ - if (heightCalcModeDefault !== heightCalcMode){ - if (!(heightCalcMode in getHeight)){ - warn(heightCalcMode + ' is not a valid option for heightCalculationMethod.'); - heightCalcMode='bodyScroll'; + function checkCalcMode(calcMode,calcModeDefault,modes,type) { + if (calcModeDefault !== calcMode) { + if (!(calcMode in modes)) { + warn(calcMode + ' is not a valid option for '+type+'CalculationMethod.'); + calcMode=calcModeDefault; } - log('Height calculation method set to "'+heightCalcMode+'"'); + log(type+' calculation method set to "'+calcMode+'"'); } + + return calcMode; + } + + function checkHeightMode() { + heightCalcMode = checkCalcMode(heightCalcMode,heightCalcModeDefault,getHeight,'height'); + } + + function checkWidthMode() { + widthCalcMode = checkCalcMode(widthCalcMode,widthCalcModeDefault,getWidth,'width'); } - function startEventListeners(){ + function startEventListeners() { if ( true === autoResize ) { - initEventListeners(); + manageEventListeners('add'); setupMutationObserver(); } else { @@ -190,23 +348,52 @@ } } - function injectClearFixIntoBodyElement(){ + function stopMsgsToParent() { + log('Disable outgoing messages'); + sendPermit = false; + } + + function removeMsgListener() { + log('Remove event listener: Message'); + removeEventListener(window, 'message', receiver); + } + + function disconnectMutationObserver() { + if (null !== bodyObserver) { + /* istanbul ignore next */ // Not testable in PhantonJS + bodyObserver.disconnect(); + } + } + + function stopEventListeners() { + manageEventListeners('remove'); + disconnectMutationObserver(); + clearInterval(intervalTimer); + } + + function teardown() { + stopMsgsToParent(); + removeMsgListener(); + if (true === autoResize) stopEventListeners(); + } + + function injectClearFixIntoBodyElement() { var clearFix = document.createElement('div'); - clearFix.style.clear = 'both'; + clearFix.style.clear = 'both'; clearFix.style.display = 'block'; //Guard against this having been globally redefined in CSS. document.body.appendChild(clearFix); } - function setupInPageLinks(){ + function setupInPageLinks() { - function getPagePosition (){ + function getPagePosition () { return { x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft, y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop }; } - function getElementPosition(el){ + function getElementPosition(el) { var elPosition = el.getBoundingClientRect(), pagePosition = getPagePosition(); @@ -217,20 +404,20 @@ }; } - function findTarget(location){ - var hash = location.split("#")[1] || ""; - var hashData = decodeURIComponent(hash); - - function jumpToTarget(target){ + function findTarget(location) { + function jumpToTarget(target) { var jumpPosition = getElementPosition(target); log('Moving to in page link (#'+hash+') at x: '+jumpPosition.x+' y: '+jumpPosition.y); sendMsg(jumpPosition.y, jumpPosition.x, 'scrollToOffset'); // X&Y reversed at sendMsg uses height/width } - var target = document.getElementById(hashData) || document.getElementsByName(hashData)[0]; + var + hash = location.split('#')[1] || location, //Remove # if present + hashData = decodeURIComponent(hash), + target = document.getElementById(hashData) || document.getElementsByName(hashData)[0]; - if (target){ + if (undefined !== target) { jumpToTarget(target); } else { log('In page link (#' + hash + ') not found in iFrame, so sending to parent'); @@ -238,22 +425,22 @@ } } - function checkLocationHash(){ - if ('' !== location.hash && '#' !== location.hash){ + function checkLocationHash() { + if ('' !== location.hash && '#' !== location.hash) { findTarget(location.href); } } - function bindAnchors(){ - function setupLink(el){ - function linkClicked(e){ + function bindAnchors() { + function setupLink(el) { + function linkClicked(e) { e.preventDefault(); /*jshint validthis:true */ findTarget(this.getAttribute('href')); } - if ('#' !== el.getAttribute('href')){ + if ('#' !== el.getAttribute('href')) { addEventListener(el,'click',linkClicked); } } @@ -261,16 +448,17 @@ Array.prototype.forEach.call( document.querySelectorAll( 'a[href^="#"]' ), setupLink ); } - function bindLocationHash(){ + function bindLocationHash() { addEventListener(window,'hashchange',checkLocationHash); } - function initCheck(){ //check if page loaded with location hash after init resize + function initCheck() { //check if page loaded with location hash after init resize setTimeout(checkLocationHash,eventCancelTimer); } - function enableInPageLinks(){ - if(Array.prototype.forEach && document.querySelectorAll){ + function enableInPageLinks() { + /* istanbul ignore else */ // Not testable in phantonJS + if(Array.prototype.forEach && document.querySelectorAll) { log('Setting up location.hash handlers'); bindAnchors(); bindLocationHash(); @@ -280,7 +468,7 @@ } } - if(inPageLinks.enable){ + if(inPageLinks.enable) { enableInPageLinks(); } else { log('In page linking not enabled'); @@ -291,85 +479,149 @@ }; } - function setupPublicMethods(){ - if (publicMethods) { - log('Enable public methods'); - - window.parentIFrame = { - close: function closeF(){ - sendMsg(0,0,'close'); - }, - getId: function getIdF(){ - return myID; - }, - moveToAnchor: function moveToAnchorF(hash){ - inPageLinks.findTarget(hash); - }, - reset: function resetF(){ - resetIFrame('parentIFrame.reset'); - }, - scrollTo: function scrollToF(x,y){ - sendMsg(y,x,'scrollTo'); // X&Y reversed at sendMsg uses height/width - }, - scrollToOffset: function scrollToF(x,y){ - sendMsg(y,x,'scrollToOffset'); // X&Y reversed at sendMsg uses height/width - }, - sendMessage: function sendMessageF(msg,targetOrigin){ - sendMsg(0,0,'message',JSON.stringify(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 setupPublicMethods() { + log('Enable public methods'); + + win.parentIFrame = { + + autoResize: function autoResizeF(resize) { + if (true === resize && false === autoResize) { + autoResize=true; + startEventListeners(); + //sendSize('autoResize','Auto Resize enabled'); + } else if (false === resize && true === autoResize) { + autoResize=false; + stopEventListeners(); } - }; - } + + return autoResize; + }, + + close: function closeF() { + sendMsg(0,0,'close'); + teardown(); + }, + + getId: function getIdF() { + return myID; + }, + + getPageInfo: function getPageInfoF(callback) { + if ('function' === typeof callback) { + pageInfoCallback = callback; + sendMsg(0,0,'pageInfo'); + } else { + pageInfoCallback = function() {}; + sendMsg(0,0,'pageInfoStop'); + } + }, + + moveToAnchor: function moveToAnchorF(hash) { + inPageLinks.findTarget(hash); + }, + + reset: function resetF() { + resetIFrame('parentIFrame.reset'); + }, + + scrollTo: function scrollToF(x,y) { + sendMsg(y,x,'scrollTo'); // X&Y reversed at sendMsg uses height/width + }, + + scrollToOffset: function scrollToF(x,y) { + sendMsg(y,x,'scrollToOffset'); // X&Y reversed at sendMsg uses height/width + }, + + sendMessage: function sendMessageF(msg,targetOrigin) { + sendMsg(0,0,'message',JSON.stringify(msg),targetOrigin); + }, + + setHeightCalculationMethod: function setHeightCalculationMethodF(heightCalculationMethod) { + heightCalcMode = heightCalculationMethod; + checkHeightMode(); + }, + + setWidthCalculationMethod: function setWidthCalculationMethodF(widthCalculationMethod) { + widthCalcMode = widthCalculationMethod; + checkWidthMode(); + }, + + 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 ){ + function initInterval() { + if ( 0 !== interval ) { log('setInterval: '+interval+'ms'); - setInterval(function(){ + intervalTimer = setInterval(function() { sendSize('interval','setInterval: '+interval); },Math.abs(interval)); } } - function setupInjectElementLoadListeners(mutations){ - function addLoadListener(element){ - if (element.height === undefined || element.width === undefined || 0 === element.height || 0 === element.width){ - log('Attach listener to '+element.src); - addEventListener(element,'load', function imageLoaded(){ - sendSize('imageLoad','Image loaded'); - }); + /* istanbul ignore next */ //Not testable in PhantomJS + function setupBodyMutationObserver() { + function addImageLoadListners(mutation) { + function addImageLoadListener(element) { + if (false === element.complete) { + log('Attach listeners to ' + element.src); + element.addEventListener('load', imageLoaded, false); + element.addEventListener('error', imageError, false); + elements.push(element); + } } - } - 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); - }); + if (mutation.type === 'attributes' && mutation.attributeName === 'src') { + addImageLoadListener(mutation.target); + } else if (mutation.type === 'childList') { + Array.prototype.forEach.call( + mutation.target.querySelectorAll('img'), + addImageLoadListener + ); } - }); - } + } + + function removeFromArray(element) { + elements.splice(elements.indexOf(element),1); + } + + function removeImageLoadListener(element) { + log('Remove listeners from ' + element.src); + element.removeEventListener('load', imageLoaded, false); + element.removeEventListener('error', imageError, false); + removeFromArray(element); + } + + function imageEventTriggered(event,type,typeDesc) { + removeImageLoadListener(event.target); + sendSize(type, typeDesc + ': ' + event.target.src, undefined, undefined); + } + + function imageLoaded(event) { + imageEventTriggered(event,'imageLoad','Image loaded'); + } - function setupMutationObserver(){ + function imageError(event) { + imageEventTriggered(event,'imageLoadFailed','Image load failed'); + } + + function mutationObserved(mutations) { + sendSize('mutationObserver','mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type); - var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; + //Deal with WebKit asyncing image loading when tags are injected into the page + mutations.forEach(addImageLoadListners); + } - function createMutationObserver(){ + function createMutationObserver() { var target = document.querySelector('body'), @@ -380,26 +632,44 @@ characterDataOldValue : false, childList : true, subtree : true - }, + }; - observer = new MutationObserver(function(mutations) { - sendSize('mutationObserver','mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type); - setupInjectElementLoadListeners(mutations); //Deal with WebKit asyncing image loading when tags are injected into the page - }); + observer = new MutationObserver(mutationObserved); - log('Enable MutationObserver'); + log('Create body MutationObserver'); observer.observe(target, config); + + return observer; } - if (MutationObserver){ - if (0 > interval) { + var + elements = [], + MutationObserver = window.MutationObserver || window.WebKitMutationObserver, + observer = createMutationObserver(); + + return { + disconnect: function () { + if ('disconnect' in observer) { + log('Disconnect body MutationObserver'); + observer.disconnect(); + elements.forEach(removeImageLoadListener); + } + } + }; + } + + function setupMutationObserver() { + var forceIntervalTimer = 0 > interval; + + /* istanbul ignore if */ // Not testable in PhantomJS + if (window.MutationObserver || window.WebKitMutationObserver) { + if (forceIntervalTimer) { initInterval(); } else { - createMutationObserver(); + bodyObserver = setupBodyMutationObserver(); } - } - else { - warn('MutationObserver not supported in this browser!'); + } else { + log('MutationObserver not supported in this browser!'); initInterval(); } } @@ -407,211 +677,288 @@ // 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; + function getComputedStyle(prop,el) { + /* istanbul ignore next */ //Not testable in PhantomJS + function convertUnitsToPxForIE8(value) { + var PIXEL = /^\d+(px)?$/i; - 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; + if (PIXEL.test(value)) { + return parseInt(value,base); } 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]); - } + 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 parseInt(retVal,base); + return value; } - return document.body.offsetHeight + - getComputedBodyStyle('marginTop') + - getComputedBodyStyle('marginBottom'); - } + var retVal = 0; + el = el || document.body; - function getBodyScrollHeight(){ - return document.body.scrollHeight; - } + /* istanbul ignore else */ // Not testable in phantonJS + 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]); + } - function getDEOffsetHeight(){ - return document.documentElement.offsetHeight; + return parseInt(retVal,base); } - function getDEScrollHeight(){ - return document.documentElement.scrollHeight; + function chkEventThottle(timer) { + if(timer > throttledTimer/2) { + throttledTimer = 2*timer; + log('Event throttle increased to ' + throttledTimer + 'ms'); + } } - //From https://github.com/guardian/iframe-messenger - function getLowestElementHeight() { + //Idea from https://github.com/guardian/iframe-messenger + function getMaxElement(side,elements) { 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; + elementsLength = elements.length, + elVal = 0, + maxVal = 0, + Side = capitalizeFirstLetter(side), + timer = getNow(); + + for (var i = 0; i < elementsLength; i++) { + elVal = elements[i].getBoundingClientRect()[side] + getComputedStyle('margin'+Side,elements[i]); + if (elVal > maxVal) { + maxVal = elVal; } } - timer = new Date().getTime() - timer; + timer = getNow() - timer; + + log('Parsed '+elementsLength+' HTML elements'); + log('Element position calculated in ' + timer + 'ms'); - log('Parsed '+allElementsLength+' HTML elements'); - log('LowestElement bottom position calculated in ' + timer + 'ms'); + chkEventThottle(timer); - return maxBottomVal; + return maxVal; } - function getAllHeights(){ + function getAllMeasurements(dimention) { return [ - getBodyOffsetHeight(), - getBodyScrollHeight(), - getDEOffsetHeight(), - getDEScrollHeight() + dimention.bodyOffset(), + dimention.bodyScroll(), + dimention.documentElementOffset(), + dimention.documentElementScroll() ]; } - function getMaxHeight(){ - return Math.max.apply(null,getAllHeights()); - } + function getTaggedElements(side,tag) { + function noTaggedElementsFound() { + warn('No tagged elements ('+tag+') found on page'); + return document.querySelectorAll('body *'); + } + + var elements = document.querySelectorAll('['+tag+']'); + + if (0 === elements.length) noTaggedElementsFound(); - function getMinHeight(){ - return Math.min.apply(null,getAllHeights()); + return getMaxElement(side,elements); } - function getBestHeight(){ - return Math.max(getBodyOffsetHeight(),getLowestElementHeight()); + function getAllElements() { + return document.querySelectorAll('body *'); } - 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 - }; + var + getHeight = { + bodyOffset: function getBodyOffsetHeight() { + return document.body.offsetHeight + getComputedStyle('marginTop') + getComputedStyle('marginBottom'); + }, - function getWidth(){ - return Math.max( - document.documentElement.scrollWidth, - document.body.scrollWidth - ); - } + offset: function() { + return getHeight.bodyOffset(); //Backwards compatability + }, - function sendSize(triggerEvent, triggerEventDesc, customHeight, customWidth){ + bodyScroll: function getBodyScrollHeight() { + return document.body.scrollHeight; + }, - var currentHeight,currentWidth; + custom: function getCustomWidth() { + return customCalcMethods.height(); + }, - function recordTrigger(){ - if (!(triggerEvent in {'reset':1,'resetPage':1,'init':1})){ - log( 'Trigger event: ' + triggerEventDesc ); + documentElementOffset: function getDEOffsetHeight() { + return document.documentElement.offsetHeight; + }, + + documentElementScroll: function getDEScrollHeight() { + return document.documentElement.scrollHeight; + }, + + max: function getMaxHeight() { + return Math.max.apply(null,getAllMeasurements(getHeight)); + }, + + min: function getMinHeight() { + return Math.min.apply(null,getAllMeasurements(getHeight)); + }, + + grow: function growHeight() { + return getHeight.max(); //Run max without the forced downsizing + }, + + lowestElement: function getBestHeight() { + return Math.max(getHeight.bodyOffset(), getMaxElement('bottom',getAllElements())); + }, + + taggedElement: function getTaggedElementsHeight() { + return getTaggedElements('bottom','data-iframe-height'); } - } + }, + + getWidth = { + bodyScroll: function getBodyScrollWidth() { + return document.body.scrollWidth; + }, + + bodyOffset: function getBodyOffsetWidth() { + return document.body.offsetWidth; + }, + + custom: function getCustomWidth() { + return customCalcMethods.width(); + }, + + documentElementScroll: function getDEScrollWidth() { + return document.documentElement.scrollWidth; + }, + + documentElementOffset: function getDEOffsetWidth() { + return document.documentElement.offsetWidth; + }, + + scroll: function getMaxWidth() { + return Math.max(getWidth.bodyScroll(), getWidth.documentElementScroll()); + }, - function resizeIFrame(){ + max: function getMaxWidth() { + return Math.max.apply(null,getAllMeasurements(getWidth)); + }, + + min: function getMinWidth() { + return Math.min.apply(null,getAllMeasurements(getWidth)); + }, + + rightMostElement: function rightMostElement() { + return getMaxElement('right', getAllElements()); + }, + + taggedElement: function getTaggedElementsWidth() { + return getTaggedElements('right', 'data-iframe-width'); + } + }; + + + function sizeIFrame(triggerEvent, triggerEventDesc, customHeight, customWidth) { + + function resizeIFrame() { height = currentHeight; width = currentWidth; sendMsg(height,width,triggerEvent); } - function isDoubleFiredEvent(){ - return triggerLocked && (triggerEvent in doubleEventList); - } - - function isSizeChangeDetected(){ - function checkTolerance(a,b){ + 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(); + currentWidth = (undefined !== customWidth ) ? customWidth : getWidth[widthCalcMode](); - return checkTolerance(height,currentHeight) || - (calculateWidth && checkTolerance(width,currentWidth)); + return checkTolarance(height,currentHeight) || (calculateWidth && checkTolarance(width,currentWidth)); } - function isForceResizableEvent(){ + function isForceResizableEvent() { return !(triggerEvent in {'init':1,'interval':1,'size':1}); } - function isForceResizableHeightCalcMode(){ - return (heightCalcMode in resetRequiredMethods); + function isForceResizableCalcMode() { + return (heightCalcMode in resetRequiredMethods) || (calculateWidth && widthCalcMode in resetRequiredMethods); } - function logIgnored(){ + function logIgnored() { log('No change in size detected'); } - function checkDownSizing(){ - if (isForceResizableEvent() && isForceResizableHeightCalcMode()){ + function checkDownSizing() { + if (isForceResizableEvent() && isForceResizableCalcMode()) { resetIFrame(triggerEventDesc); - } else if (!(triggerEvent in {'interval':1})){ - recordTrigger(); + } else if (!(triggerEvent in {'interval':1})) { logIgnored(); } } - if (!isDoubleFiredEvent()){ - if (isSizeChangeDetected()){ - recordTrigger(); - lockTrigger(); - resizeIFrame(); + var currentHeight,currentWidth; + + if (isSizeChangeDetected() || 'init' === triggerEvent) { + lockTrigger(); + resizeIFrame(); + } else { + checkDownSizing(); + } + } + + var sizeIFrameThrottled = throttle(sizeIFrame); + + function sendSize(triggerEvent, triggerEventDesc, customHeight, customWidth) { + function recordTrigger() { + if (!(triggerEvent in {'reset':1,'resetPage':1,'init':1})) { + log( 'Trigger event: ' + triggerEventDesc ); + } + } + + function isDoubleFiredEvent() { + return triggerLocked && (triggerEvent in doubleEventList); + } + + if (!isDoubleFiredEvent()) { + recordTrigger(); + if (triggerEvent === 'init') { + sizeIFrame(triggerEvent, triggerEventDesc, customHeight, customWidth); } else { - checkDownSizing(); + sizeIFrameThrottled(triggerEvent, triggerEventDesc, customHeight, customWidth); } } else { log('Trigger event cancelled: '+triggerEvent); } } - function lockTrigger(){ - if (!triggerLocked){ + function lockTrigger() { + if (!triggerLocked) { triggerLocked = true; log('Trigger event lock on'); } clearTimeout(triggerLockedTimer); - triggerLockedTimer = setTimeout(function(){ + triggerLockedTimer = setTimeout(function() { triggerLocked = false; log('Trigger event lock off'); log('--'); },eventCancelTimer); } - function triggerReset(triggerEvent){ + function triggerReset(triggerEvent) { height = getHeight[heightCalcMode](); - width = getWidth(); + width = getWidth[widthCalcMode](); sendMsg(height,width,triggerEvent); } - function resetIFrame(triggerEventDesc){ + function resetIFrame(triggerEventDesc) { var hcm = heightCalcMode; heightCalcMode = heightCalcModeDefault; @@ -622,16 +969,16 @@ heightCalcMode = hcm; } - function sendMsg(height,width,triggerEvent,msg,targetOrigin){ - function setTargetOrigin(){ - if (undefined === targetOrigin){ + function sendMsg(height,width,triggerEvent,msg,targetOrigin) { + function setTargetOrigin() { + if (undefined === targetOrigin) { targetOrigin = targetOriginDefault; } else { log('Message targetOrigin: '+targetOrigin); } } - function sendToParent(){ + function sendToParent() { var size = height + ':' + width, message = myID + ':' + size + ':' + triggerEvent + (undefined !== msg ? ':' + msg : ''); @@ -640,68 +987,124 @@ target.postMessage( msgID + message, targetOrigin); } - setTargetOrigin(); - sendToParent(); + if(true === sendPermit) { + setTargetOrigin(); + sendToParent(); + } } function receiver(event) { - function isMessageForUs(){ - return msgID === (''+event.data).substr(0,msgIdLen); //''+ Protects against non-string messages - } + var processRequestFromParent = { + init: function initFromParent() { + function fireInit() { + initMsg = event.data; + target = event.source; + + init(); + firstRun = false; + setTimeout(function() { initLock = false;},eventCancelTimer); + } - function initFromParent(){ - initMsg = event.data; - target = event.source; + if (document.readyState === "interactive" || document.readyState === "complete") { + fireInit(); + } else { + log('Waiting for page ready'); + addEventListener(window,'readystatechange',processRequestFromParent.initFromParent); + } + }, + + reset: function resetFromParent() { + if (!initLock) { + log('Page size reset by host page'); + triggerReset('resetPage'); + } else { + log('Page reset ignored by init'); + } + }, + + resize: function resizeFromParent() { + sendSize('resizeParent','Parent window requested size check'); + }, + + moveToAnchor: function moveToAnchorF() { + inPageLinks.findTarget(getData()); + }, + inPageLink: function inPageLinkF() {this.moveToAnchor();}, //Backward compatability + + pageInfo: function pageInfoFromParent() { + var msgBody = getData(); + log('PageInfoFromParent called from parent: ' + msgBody ); + pageInfoCallback(JSON.parse(msgBody)); + log(' --'); + }, + + message: function messageFromParent() { + var msgBody = getData(); + + log('MessageCallback called from parent: ' + msgBody ); + messageCallback(JSON.parse(msgBody)); + log(' --'); + } + }; - init(); - firstRun = false; - setTimeout(function(){ initLock = false;},eventCancelTimer); + function isMessageForUs() { + return msgID === (''+event.data).substr(0,msgIdLen); //''+ Protects against non-string messages } - 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].split(':')[0]; } - function resizeFromParent(){ - sendSize('resizeParent','Parent window resized'); + function getData() { + return event.data.substr(event.data.indexOf(':')+1); } - function getMessageType(){ - return event.data.split(']')[1]; + function isMiddleTier() { + return !(typeof module !== 'undefined' && module.exports) && ('iFrameResize' in window); } - 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}; } - 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 compatibility. - return event.data.split(':')[2] in {'true':1,'false':1}; + function callFromParent() { + var messageType = getMessageType(); + + if (messageType in processRequestFromParent) { + processRequestFromParent[messageType](); + } else if (!isMiddleTier() && !isInitMsg()) { + warn('Unexpected message ('+event.data+')'); + } } - if (isMessageForUs()){ - if (firstRun === false) { - if ('reset' === getMessageType()){ - resetFromParent(); - } else if ('resize' === getMessageType()){ - resizeFromParent(); - } else if (event.data !== initMsg && !isMiddleTier()){ - warn('Unexpected message ('+event.data+')'); - } + function processMessage() { + if (false === firstRun) { + callFromParent(); } else if (isInitMsg()) { - initFromParent(); + processRequestFromParent.init(); } else { - warn('Received message of type ('+getMessageType()+') before initialization.'); + log('Ignored message of type "' + getMessageType() + '". Received before initialization.'); } } + + if (isMessageForUs()) { + processMessage(); + } + } + + //Normally the parent kicks things off when it detects the iFrame has loaded. + //If this script is async-loaded, then tell parent page to retry init. + function chkLateLoaded() { + if('loading' !== document.readyState) { + window.parent.postMessage('[iFrameResizerChild]Ready','*'); + } } addEventListener(window, 'message', receiver); + chkLateLoaded(); + + -})(); \ No newline at end of file +})(); diff --git a/examples/assets/library/iframe.js b/examples/assets/library/iframe.js index 2137604eb..4173879ae 100644 --- a/examples/assets/library/iframe.js +++ b/examples/assets/library/iframe.js @@ -2,17 +2,22 @@ * File: iframeResizer.js * Desc: Force iframes to size to content. * Requires: iframeResizer.contentWindow.js to be loaded into the target frame. + * Doc: https://github.com/davidjbradshaw/iframe-resizer * Author: David J. Bradshaw - dave@bradshaw.net * Contributor: Jure Mav - jure.mav@gmail.com * Contributor: Reed Dadoune - reed@dadoune.com */ -;(function() { + + +;(function(undefined) { 'use strict'; + if(typeof window === 'undefined') return; // don't run for server side render + var count = 0, - firstRun = true, logEnabled = false, + hiddenCheckEnabled = false, msgHeader = 'message', msgHeaderLen = msgHeader.length, msgId = '[iFrameSizer]', //Must match iframe msg ID @@ -22,6 +27,7 @@ resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1}, settings = {}, timer = null, + logId = 'Host Page', defaults = { autoResize : true, @@ -30,9 +36,10 @@ bodyMarginV1 : 8, bodyPadding : null, checkOrigin : true, - enableInPageLinks : false, - enablePublicMethods : false, - heightCalculationMethod : 'offset', + inPageLinks : false, + enablePublicMethods : true, + heightCalculationMethod : 'bodyOffset', + id : 'iFrameResizer', interval : 32, log : false, maxHeight : Infinity, @@ -43,23 +50,35 @@ scrolling : false, sizeHeight : true, sizeWidth : false, + warningTimeout : 5000, tolerance : 0, - closedCallback : function(){}, - initCallback : function(){}, - messageCallback : function(){}, - resizedCallback : function(){}, - scrollCallback : function(){return true;} + widthCalculationMethod : 'scroll', + closedCallback : function() {}, + initCallback : function() {}, + messageCallback : function() {warn('MessageCallback function not defined');}, + resizedCallback : function() {}, + scrollCallback : function() {return true;} }; - function addEventListener(obj,evt,func){ - if ('addEventListener' in window){ + function addEventListener(obj,evt,func) { + /* istanbul ignore else */ // Not testable in PhantonJS + if ('addEventListener' in window) { obj.addEventListener(evt,func, false); - } else if ('attachEvent' in window){//IE + } else if ('attachEvent' in window) {//IE obj.attachEvent('on'+evt,func); } } - function setupRequestAnimationFrame(){ + function removeEventListener(el,evt,func) { + /* istanbul ignore else */ // Not testable in phantonJS + if ('removeEventListener' in window) { + el.removeEventListener(evt,func, false); + } else if ('detachEvent' in window) { //IE + el.detachEvent('on'+evt,func); + } + } + + function setupRequestAnimationFrame() { var vendors = ['moz', 'webkit', 'o', 'ms'], x; @@ -69,70 +88,70 @@ requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame']; } - if (!(requestAnimationFrame)){ - log(' RequestAnimationFrame not supported'); + if (!(requestAnimationFrame)) { + log('setup','RequestAnimationFrame not supported'); } } - function getMyID(){ - var retStr = 'Host page'; + function getMyID(iframeId) { + var retStr = 'Host page: '+iframeId; - if (window.top!==window.self){ - if (window.parentIFrame){ - retStr = window.parentIFrame.getId(); + if (window.top !== window.self) { + if (window.parentIFrame && window.parentIFrame.getId) { + retStr = window.parentIFrame.getId()+': '+iframeId; } else { - retStr = 'Nested host page'; + retStr = 'Nested host page: '+iframeId; } } return retStr; } - function formatLogMsg(msg){ - return msgId + '[' + getMyID() + ']' + msg; + function formatLogHeader(iframeId) { + return msgId + '[' + getMyID(iframeId) + ']'; } - function log(msg){ - if (logEnabled && ('object' === typeof window.console)){ - console.log(formatLogMsg(msg)); - } + function isLogEnabled(iframeId) { + return settings[iframeId] ? settings[iframeId].log : logEnabled; } - function warn(msg){ - if ('object' === typeof window.console){ - console.warn(formatLogMsg(msg)); + function log(iframeId,msg) { + output('log',iframeId,msg,isLogEnabled(iframeId)); + } + + function info(iframeId,msg) { + output('info',iframeId,msg,isLogEnabled(iframeId)); + } + + function warn(iframeId,msg) { + output('warn',iframeId,msg,true); + } + + function output(type,iframeId,msg,enabled) { + if (true === enabled && 'object' === typeof window.console) { + console[type](formatLogHeader(iframeId),msg); } } - function iFrameListener(event){ - function resizeIFrame(){ - function resize(){ + function iFrameListener(event) { + function resizeIFrame() { + function resize() { setSize(messageData); - setPagePosition(); - settings[iframeID].resizedCallback(messageData); + setPagePosition(iframeId); + callback('resizedCallback',messageData); } ensureInRange('Height'); ensureInRange('Width'); - syncResize(resize,messageData,'resetPage'); - } - - function closeIFrame(iframe){ - var iframeID = iframe.id; - - log(' Removing iFrame: '+iframeID); - iframe.parentNode.removeChild(iframe); - settings[iframeID].closedCallback(iframeID); - delete settings[iframeID]; - log(' --'); + syncResize(resize,messageData,'init'); } - function processMsg(){ + function processMsg() { var data = msg.substr(msgIdLen).split(':'); return { - iframe: document.getElementById(data[0]), + iframe: settings[data[0]] && settings[data[0]].iframe, id: data[0], height: data[1], width: data[2], @@ -140,496 +159,887 @@ }; } - function ensureInRange(Dimension){ + function ensureInRange(Dimension) { var - max = Number(settings[iframeID]['max'+Dimension]), - min = Number(settings[iframeID]['min'+Dimension]), + max = Number(settings[iframeId]['max' + Dimension]), + min = Number(settings[iframeId]['min' + Dimension]), dimension = Dimension.toLowerCase(), size = Number(messageData[dimension]); - if (min>max){ - throw new Error('Value for min'+Dimension+' can not be greater than max'+Dimension); - } - - log(' Checking '+dimension+' is in range '+min+'-'+max); + log(iframeId,'Checking ' + dimension + ' is in range ' + min + '-' + max); if (sizemax) { size=max; - log(' Set '+dimension+' to max value'); + log(iframeId,'Set ' + dimension + ' to max value'); } - messageData[dimension]=''+size; + messageData[dimension] = '' + size; } - function isMessageFromIFrame(){ - function checkAllowedOrigin(){ - function checkList(){ - log(' Checking connection is from allowed list of origins: ' + checkOrigin); - var i; - for (i = 0; i < checkOrigin.length; i++) { + function isMessageFromIFrame() { + function checkAllowedOrigin() { + function checkList() { + var + i = 0, + retCode = false; + + log(iframeId,'Checking connection is from allowed list of origins: ' + checkOrigin); + + for (; i < checkOrigin.length; i++) { if (checkOrigin[i] === origin) { - return true; + retCode = true; + break; } } - return false; + return retCode; } - function checkSingle(){ - log(' Checking connection is from: '+remoteHost); - return origin == remoteHost; + function checkSingle() { + var remoteHost = settings[iframeId] && settings[iframeId].remoteHost; + log(iframeId,'Checking connection is from: '+remoteHost); + return origin === remoteHost; } return checkOrigin.constructor === Array ? checkList() : checkSingle(); } var - origin = event.origin, - checkOrigin = settings[iframeID].checkOrigin, - remoteHost = messageData.iframe.src.split('/').slice(0,3).join('/'); - - if (checkOrigin) { - if ((''+origin !== 'null') && !checkAllowedOrigin()) { - throw new Error( - 'Unexpected message received from: ' + origin + - ' for ' + messageData.iframe.id + - '. Message was: ' + event.data + - '. This error can be disabled by setting the checkOrigin: false option or by providing of array of trusted domains.' - ); - } + origin = event.origin, + checkOrigin = settings[iframeId] && settings[iframeId].checkOrigin; + + if (checkOrigin && (''+origin !== 'null') && !checkAllowedOrigin()) { + throw new Error( + 'Unexpected message received from: ' + origin + + ' for ' + messageData.iframe.id + + '. Message was: ' + event.data + + '. This error can be disabled by setting the checkOrigin: false option or by providing of array of trusted domains.' + ); } return true; } - function isMessageForUs(){ - return msgId === ('' + msg).substr(0,msgIdLen); //''+Protects against non-string msg + function isMessageForUs() { + return msgId === (('' + msg).substr(0,msgIdLen)) && (msg.substr(msgIdLen).split(':')[0] in settings); //''+Protects against non-string msg } - function isMessageFromMetaParent(){ - //test if this message is from a parent above us. This is an ugly test, however, updating - //the message format would break backwards compatibility. + function isMessageFromMetaParent() { + //Test if this message is from a parent above us. This is an ugly test, however, updating + //the message format would break backwards compatibity. var retCode = messageData.type in {'true':1,'false':1,'undefined':1}; - if (retCode){ - log(' Ignoring init message from meta parent page'); + if (retCode) { + log(iframeId,'Ignoring init message from meta parent page'); } return retCode; } - function getMsgBody(offset){ + function getMsgBody(offset) { return msg.substr(msg.indexOf(':')+msgHeaderLen+offset); } - function forwardMsgFromIFrame(msgBody){ - log(' MessageCallback passed: {iframe: '+ messageData.iframe.id + ', message: ' + msgBody + '}'); - settings[iframeID].messageCallback({ + function forwardMsgFromIFrame(msgBody) { + log(iframeId,'MessageCallback passed: {iframe: '+ messageData.iframe.id + ', message: ' + msgBody + '}'); + callback('messageCallback',{ iframe: messageData.iframe, message: JSON.parse(msgBody) }); - log(' --'); + log(iframeId,'--'); + } + + function getPageInfo() { + var + bodyPosition = document.body.getBoundingClientRect(), + iFramePosition = messageData.iframe.getBoundingClientRect(); + + return JSON.stringify({ + iframeHeight: iFramePosition.height, + iframeWidth: iFramePosition.width, + clientHeight: Math.max(document.documentElement.clientHeight, window.innerHeight || 0), + clientWidth: Math.max(document.documentElement.clientWidth, window.innerWidth || 0), + offsetTop: parseInt(iFramePosition.top - bodyPosition.top, 10), + offsetLeft: parseInt(iFramePosition.left - bodyPosition.left, 10), + scrollTop: window.pageYOffset, + scrollLeft: window.pageXOffset + }); + } + + function sendPageInfoToIframe(iframe,iframeId) { + function debouncedTrigger() { + trigger( + 'Send Page Info', + 'pageInfo:' + getPageInfo(), + iframe, + iframeId + ); + } + + debouce(debouncedTrigger,32); + } + + + function startPageInfoMonitor() { + function setListener(type,func) { + function sendPageInfo() { + if (settings[id]) { + sendPageInfoToIframe(settings[id].iframe,id); + } else { + stop(); + } + } + + ['scroll','resize'].forEach(function(evt) { + log(id, type + evt + ' listener for sendPageInfo'); + func(window,evt,sendPageInfo); + }); + } + + function stop() { + setListener('Remove ', removeEventListener); + } + + function start() { + setListener('Add ', addEventListener); + } + + var id = iframeId; //Create locally scoped copy of iFrame ID + + start(); + + if (settings[id]) { + settings[id].stopPageInfo = stop; + } } - function checkIFrameExists(){ + function stopPageInfoMonitor() { + if (settings[iframeId] && settings[iframeId].stopPageInfo) { + settings[iframeId].stopPageInfo(); + delete settings[iframeId].stopPageInfo; + } + } + + function checkIFrameExists() { + var retBool = true; + if (null === messageData.iframe) { - warn(' IFrame ('+messageData.id+') not found'); - return false; + warn(iframeId,'IFrame ('+messageData.id+') not found'); + retBool = false; } - return true; + return retBool; } - function getElementPosition(target){ - var - iFramePosition = target.getBoundingClientRect(); + function getElementPosition(target) { + var iFramePosition = target.getBoundingClientRect(); - getPagePosition(); + getPagePosition(iframeId); return { - x: parseInt(iFramePosition.left, 10) + parseInt(pagePosition.x, 10), - y: parseInt(iFramePosition.top, 10) + parseInt(pagePosition.y, 10) + x: Math.floor( Number(iFramePosition.left) + Number(pagePosition.x) ), + y: Math.floor( Number(iFramePosition.top) + Number(pagePosition.y) ) }; } - function scrollRequestFromChild(addOffset){ - function reposition(){ + function scrollRequestFromChild(addOffset) { + /* istanbul ignore next */ //Not testable in Karma + function reposition() { pagePosition = newPosition; - scrollTo(); - - log(' --'); + log(iframeId,'--'); } - function calcOffset(){ + function calcOffset() { return { x: Number(messageData.width) + offset.x, y: Number(messageData.height) + offset.y }; } + function scrollParent() { + if (window.parentIFrame) { + window.parentIFrame['scrollTo'+(addOffset?'Offset':'')](newPosition.x,newPosition.y); + } else { + warn(iframeId,'Unable to scroll to requested position, window.parentIFrame not found'); + } + } + var offset = addOffset ? getElementPosition(messageData.iframe) : {x:0,y:0}, newPosition = calcOffset(); - log(' Reposition requested from iFrame (offset x:'+offset.x+' y:'+offset.y+')'); + log(iframeId,'Reposition requested from iFrame (offset x:'+offset.x+' y:'+offset.y+')'); - if(window.top!==window.self){ - if (window.parentIFrame){ - if (addOffset){ - parentIFrame.scrollToOffset(newPosition.x,newPosition.y); - } else { - parentIFrame.scrollTo(messageData.width,messageData.height); - } - } else { - warn(' Unable to scroll to requested position, window.parentIFrame not found'); - } + if(window.top !== window.self) { + scrollParent(); } else { reposition(); } - } - function scrollTo(){ - if (false !== settings[iframeID].scrollCallback(pagePosition)){ - setPagePosition(); + function scrollTo() { + if (false !== callback('scrollCallback',pagePosition)) { + setPagePosition(iframeId); + } else { + unsetPagePosition(); } } - function findTarget(location){ - var hash = location.split("#")[1] || ""; - var hashData = decodeURIComponent(hash); - - function jumpToTarget(target){ + function findTarget(location) { + function jumpToTarget() { var jumpPosition = getElementPosition(target); - log(' Moving to in page link (#'+hash+') at x: '+jumpPosition.x+' y: '+jumpPosition.y); + log(iframeId,'Moving to in page link (#'+hash+') at x: '+jumpPosition.x+' y: '+jumpPosition.y); pagePosition = { x: jumpPosition.x, y: jumpPosition.y }; scrollTo(); - log(' --'); + log(iframeId,'--'); } - var target = document.getElementById(hashData) || document.getElementsByName(hashData)[0]; - - if(window.top!==window.self){ - if (window.parentIFrame){ - parentIFrame.moveToAnchor(hash); + function jumpToParent() { + if (window.parentIFrame) { + window.parentIFrame.moveToAnchor(hash); } else { - log(' In page link #'+hash+' not found and window.parentIFrame not found'); + log(iframeId,'In page link #'+hash+' not found and window.parentIFrame not found'); } - } else if (target){ - jumpToTarget(target); + } + + var + hash = location.split('#')[1] || '', + hashData = decodeURIComponent(hash), + target = document.getElementById(hashData) || document.getElementsByName(hashData)[0]; + + if (target) { + jumpToTarget(); + } else if(window.top!==window.self) { + jumpToParent(); } else { - log(' In page link #'+hash+' not found'); + log(iframeId,'In page link #'+hash+' not found'); } } - function actionMsg(){ - switch(messageData.type){ - case 'close': - closeIFrame(messageData.iframe); - break; - case 'message': - forwardMsgFromIFrame(getMsgBody(6)); - break; - case 'scrollTo': - scrollRequestFromChild(false); - break; - case 'scrollToOffset': - scrollRequestFromChild(true); - break; - case 'inPageLink': - findTarget(getMsgBody(9)); - break; - case 'reset': - resetIFrame(messageData); - break; - case 'init': - resizeIFrame(); - settings[iframeID].initCallback(messageData.iframe); - break; - default: - resizeIFrame(); + function callback(funcName,val) { + return chkCallback(iframeId,funcName,val); + } + + function actionMsg() { + + if(settings[iframeId] && settings[iframeId].firstRun) firstRun(); + + switch(messageData.type) { + case 'close': + if(settings[iframeId].closeRequestCallback) chkCallback(iframeId, 'closeRequestCallback', settings[iframeId].iframe); + else closeIFrame(messageData.iframe); + break; + case 'message': + forwardMsgFromIFrame(getMsgBody(6)); + break; + case 'scrollTo': + scrollRequestFromChild(false); + break; + case 'scrollToOffset': + scrollRequestFromChild(true); + break; + case 'pageInfo': + sendPageInfoToIframe(settings[iframeId] && settings[iframeId].iframe,iframeId); + startPageInfoMonitor(); + break; + case 'pageInfoStop': + stopPageInfoMonitor(); + break; + case 'inPageLink': + findTarget(getMsgBody(9)); + break; + case 'reset': + resetIFrame(messageData); + break; + case 'init': + resizeIFrame(); + callback('initCallback',messageData.iframe); + break; + default: + resizeIFrame(); } } - function hasSettings(iframeID){ + function hasSettings(iframeId) { var retBool = true; - if (!settings[iframeID]){ + if (!settings[iframeId]) { retBool = false; - warn(messageData.type + ' No settings for ' + iframeID + '. Message was: ' + msg); + warn(messageData.type + ' No settings for ' + iframeId + '. Message was: ' + msg); } return retBool; } + function iFrameReadyMsgReceived() { + for (var iframeId in settings) { + trigger('iFrame requested init',createOutgoingMsg(iframeId),document.getElementById(iframeId),iframeId); + } + } + + function firstRun() { + if (settings[iframeId]) { + settings[iframeId].firstRun = false; + } + } + + function clearWarningTimeout() { + if (settings[iframeId]) { + clearTimeout(settings[iframeId].msgTimeout); + settings[iframeId].warningTimeout = 0; + } + } + var msg = event.data, messageData = {}, - iframeID = null; + iframeId = null; - if (isMessageForUs()){ + if('[iFrameResizerChild]Ready' === msg) { + iFrameReadyMsgReceived(); + } else if (isMessageForUs()) { messageData = processMsg(); - iframeID = messageData.id; + iframeId = logId = messageData.id; + if (settings[iframeId]) { + settings[iframeId].loaded = true; + } - if (!isMessageFromMetaParent() && hasSettings(iframeID)){ - logEnabled = settings[iframeID].log; - log(' Received: '+msg); + if (!isMessageFromMetaParent() && hasSettings(iframeId)) { + log(iframeId,'Received: '+msg); - if ( checkIFrameExists() && isMessageFromIFrame() ){ + if ( checkIFrameExists() && isMessageFromIFrame() ) { actionMsg(); - firstRun = false; } } + } else { + info(iframeId,'Ignored: '+msg); } + } - function getPagePosition (){ - if(null === pagePosition){ + function chkCallback(iframeId,funcName,val) { + var + func = null, + retVal = null; + + if(settings[iframeId]) { + func = settings[iframeId][funcName]; + + if( 'function' === typeof func) { + retVal = func(val); + } else { + throw new TypeError(funcName+' on iFrame['+iframeId+'] is not a function'); + } + } + + return retVal; + } + + function closeIFrame(iframe) { + var iframeId = iframe.id; + + log(iframeId,'Removing iFrame: '+iframeId); + if (iframe.parentNode) { iframe.parentNode.removeChild(iframe); } + chkCallback(iframeId,'closedCallback',iframeId); + log(iframeId,'--'); + delete settings[iframeId]; + } + + function getPagePosition(iframeId) { + if(null === pagePosition) { pagePosition = { x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft, y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop }; - log(' Get page position: '+pagePosition.x+','+pagePosition.y); + log(iframeId,'Get page position: '+pagePosition.x+','+pagePosition.y); } } - function setPagePosition(){ - if(null !== pagePosition){ + function setPagePosition(iframeId) { + if(null !== pagePosition) { window.scrollTo(pagePosition.x,pagePosition.y); - log(' Set page position: '+pagePosition.x+','+pagePosition.y); - pagePosition = null; + log(iframeId,'Set page position: '+pagePosition.x+','+pagePosition.y); + unsetPagePosition(); } } - function resetIFrame(messageData){ - function reset(){ + function unsetPagePosition() { + pagePosition = null; + } + + function resetIFrame(messageData) { + function reset() { setSize(messageData); trigger('reset','reset',messageData.iframe,messageData.id); } - log(' Size reset requested by '+('init'===messageData.type?'host page':'iFrame')); - getPagePosition(); - syncResize(reset,messageData,'init'); + log(messageData.id,'Size reset requested by '+('init'===messageData.type?'host page':'iFrame')); + getPagePosition(messageData.id); + syncResize(reset,messageData,'reset'); } - function setSize(messageData){ - function setDimension(dimension){ + function setSize(messageData) { + function setDimension(dimension) { messageData.iframe.style[dimension] = messageData[dimension] + 'px'; log( - ' IFrame (' + iframeID + + messageData.id, + 'IFrame (' + iframeId + ') ' + dimension + ' set to ' + messageData[dimension] + 'px' ); } - var iframeID = messageData.iframe.id; - if( settings[iframeID].sizeHeight) { setDimension('height'); } - if( settings[iframeID].sizeWidth ) { setDimension('width'); } + + function chkZero(dimension) { + //FireFox sets dimension of hidden iFrames to zero. + //So if we detect that set up an event to check for + //when iFrame becomes visible. + + /* istanbul ignore next */ //Not testable in PhantomJS + if (!hiddenCheckEnabled && '0' === messageData[dimension]) { + hiddenCheckEnabled = true; + log(iframeId,'Hidden iFrame detected, creating visibility listener'); + fixHiddenIFrames(); + } + } + + function processDimension(dimension) { + setDimension(dimension); + chkZero(dimension); + } + + var iframeId = messageData.iframe.id; + + if(settings[iframeId]) { + if( settings[iframeId].sizeHeight) { processDimension('height'); } + if( settings[iframeId].sizeWidth ) { processDimension('width'); } + } } - function syncResize(func,messageData,doNotSync){ - if(doNotSync!==messageData.type && requestAnimationFrame){ - log(' Requesting animation frame'); + function syncResize(func,messageData,doNotSync) { + /* istanbul ignore if */ //Not testable in PhantomJS + if(doNotSync!==messageData.type && requestAnimationFrame) { + log(messageData.id,'Requesting animation frame'); requestAnimationFrame(func); } else { func(); } } - function trigger(calleeMsg,msg,iframe,id){ - if(iframe && iframe.contentWindow){ - log('[' + calleeMsg + '] Sending msg to iframe ('+msg+')'); - iframe.contentWindow.postMessage( msgId + msg, '*' ); - } else { - warn('[' + calleeMsg + '] IFrame not found'); - if(settings[id]) delete settings[id]; + function trigger(calleeMsg, msg, iframe, id, noResponseWarning) { + function postMessageToIFrame() { + var target = settings[id] && settings[id].targetOrigin; + log(id,'[' + calleeMsg + '] Sending msg to iframe['+id+'] ('+msg+') targetOrigin: '+target); + iframe.contentWindow.postMessage( msgId + msg, target ); + } + + function iFrameNotFound() { + warn(id,'[' + calleeMsg + '] IFrame('+id+') not found'); } + + function chkAndSend() { + if(iframe && 'contentWindow' in iframe && (null !== iframe.contentWindow)) { //Null test for PhantomJS + postMessageToIFrame(); + } else { + iFrameNotFound(); + } + } + + function warnOnNoResponse() { + function warning() { + if (settings[id] && !settings[id].loaded && !errorShown) { + errorShown = true; + warn(id, 'IFrame has not responded within '+ settings[id].warningTimeout/1000 +' seconds. Check iFrameResizer.contentWindow.js has been loaded in iFrame. This message can be ingored if everything is working, or you can set the warningTimeout option to a higher value or zero to suppress this warning.'); + } + } + + if (!!noResponseWarning && settings[id] && !!settings[id].warningTimeout) { + settings[id].msgTimeout = setTimeout(warning, settings[id].warningTimeout); + } + } + + var errorShown = false; + + id = id || iframe.id; + + if(settings[id]) { + chkAndSend(); + warnOnNoResponse(); + } + } + function createOutgoingMsg(iframeId) { + return iframeId + + ':' + settings[iframeId].bodyMarginV1 + + ':' + settings[iframeId].sizeWidth + + ':' + settings[iframeId].log + + ':' + settings[iframeId].interval + + ':' + settings[iframeId].enablePublicMethods + + ':' + settings[iframeId].autoResize + + ':' + settings[iframeId].bodyMargin + + ':' + settings[iframeId].heightCalculationMethod + + ':' + settings[iframeId].bodyBackground + + ':' + settings[iframeId].bodyPadding + + ':' + settings[iframeId].tolerance + + ':' + settings[iframeId].inPageLinks + + ':' + settings[iframeId].resizeFrom + + ':' + settings[iframeId].widthCalculationMethod; + } + + function setupIFrame(iframe,options) { + function setLimits() { + function addStyle(style) { + if ((Infinity !== settings[iframeId][style]) && (0 !== settings[iframeId][style])) { + iframe.style[style] = settings[iframeId][style] + 'px'; + log(iframeId,'Set '+style+' = '+settings[iframeId][style]+'px'); + } + } - function setupIFrame(options){ - function setLimits(){ - function addStyle(style){ - if ((Infinity !== settings[iframeID][style]) && (0 !== settings[iframeID][style])){ - iframe.style[style] = settings[iframeID][style] + 'px'; - log(' Set '+style+' = '+settings[iframeID][style]+'px'); + function chkMinMax(dimension) { + if (settings[iframeId]['min'+dimension]>settings[iframeId]['max'+dimension]) { + throw new Error('Value for min'+dimension+' can not be greater than max'+dimension); } } + chkMinMax('Height'); + chkMinMax('Width'); + addStyle('maxHeight'); addStyle('minHeight'); addStyle('maxWidth'); addStyle('minWidth'); } - function ensureHasId(iframeID){ - if (''===iframeID){ - iframe.id = iframeID = 'iFrameResizer' + count++; + function newId() { + var id = ((options && options.id) || defaults.id + count++); + if (null !== document.getElementById(id)) { + id = id + count++; + } + return id; + } + + function ensureHasId(iframeId) { + logId=iframeId; + if (''===iframeId) { + iframe.id = iframeId = newId(); logEnabled = (options || {}).log; - log(' Added missing iframe ID: '+ iframeID +' (' + iframe.src + ')'); + logId=iframeId; + log(iframeId,'Added missing iframe ID: '+ iframeId +' (' + iframe.src + ')'); } - return iframeID; + + return iframeId; } - function setScrolling(){ - log(' IFrame scrolling ' + (settings[iframeID].scrolling ? 'enabled' : 'disabled') + ' for ' + iframeID); - iframe.style.overflow = false === settings[iframeID].scrolling ? 'hidden' : 'auto'; - iframe.scrolling = false === settings[iframeID].scrolling ? 'no' : 'yes'; + function setScrolling() { + log(iframeId,'IFrame scrolling ' + (settings[iframeId] && settings[iframeId].scrolling ? 'enabled' : 'disabled') + ' for ' + iframeId); + iframe.style.overflow = false === (settings[iframeId] && settings[iframeId].scrolling) ? 'hidden' : 'auto'; + switch(settings[iframeId] && settings[iframeId].scrolling) { + case true: + iframe.scrolling = 'yes'; + break; + case false: + iframe.scrolling = 'no'; + break; + default: + iframe.scrolling = settings[iframeId] ? settings[iframeId].scrolling : 'no'; + } } //The V1 iFrame script expects an int, where as in V2 expects a CSS //string value such as '1px 3em', so if we have an int for V2, set V1=V2 //and then convert V2 to a string PX value. - function setupBodyMarginValues(){ - if (('number'===typeof(settings[iframeID].bodyMargin)) || ('0'===settings[iframeID].bodyMargin)){ - settings[iframeID].bodyMarginV1 = settings[iframeID].bodyMargin; - settings[iframeID].bodyMargin = '' + settings[iframeID].bodyMargin + 'px'; - } - } - - function createOutgoingMsg(){ - return iframeID + - ':' + settings[iframeID].bodyMarginV1 + - ':' + settings[iframeID].sizeWidth + - ':' + settings[iframeID].log + - ':' + settings[iframeID].interval + - ':' + settings[iframeID].enablePublicMethods + - ':' + settings[iframeID].autoResize + - ':' + settings[iframeID].bodyMargin + - ':' + settings[iframeID].heightCalculationMethod + - ':' + settings[iframeID].bodyBackground + - ':' + settings[iframeID].bodyPadding + - ':' + settings[iframeID].tolerance + - ':' + settings[iframeID].enableInPageLinks + - ':' + settings[iframeID].resizeFrom; - } - - function init(msg){ - //We have to call trigger twice, as we can not be sure if all - //iframes have completed loading when this code runs. The - //event listener also catches the page changing in the iFrame. - addEventListener(iframe,'load',function(){ - var fr = firstRun; // Reduce scope of var to function, because IE8's JS execution - // context stack is borked and this value gets externally - // changed midway through running this function. - trigger('iFrame.onload',msg,iframe); - if (!fr && settings[iframeID].heightCalculationMethod in resetRequiredMethods){ - resetIFrame({ - iframe:iframe, - height:0, - width:0, - type:'init' - }); - } - }); - trigger('init',msg,iframe); + function setupBodyMarginValues() { + if (('number'===typeof(settings[iframeId] && settings[iframeId].bodyMargin)) || ('0'===(settings[iframeId] && settings[iframeId].bodyMargin))) { + settings[iframeId].bodyMarginV1 = settings[iframeId].bodyMargin; + settings[iframeId].bodyMargin = '' + settings[iframeId].bodyMargin + 'px'; + } } - function checkOptions(options){ - if ('object' !== typeof options){ - throw new TypeError('Options is not an object.'); + function checkReset() { + // Reduce scope of firstRun to function, because IE8's JS execution + // context stack is borked and this value gets externally + // changed midway through running this function!!! + var + firstRun = settings[iframeId] && settings[iframeId].firstRun, + resetRequertMethod = settings[iframeId] && settings[iframeId].heightCalculationMethod in resetRequiredMethods; + + if (!firstRun && resetRequertMethod) { + resetIFrame({iframe:iframe, height:0, width:0, type:'init'}); } } - function processOptions(options){ - options = options || {}; - settings[iframeID] = {}; + function setupIFrameObject() { + if(Function.prototype.bind && settings[iframeId]) { //Ignore unpolyfilled IE8. + settings[iframeId].iframe.iFrameResizer = { - checkOptions(options); + close : closeIFrame.bind(null,settings[iframeId].iframe), + + resize : trigger.bind(null,'Window resize', 'resize', settings[iframeId].iframe), + + moveToAnchor : function(anchor) { + trigger('Move to anchor','moveToAnchor:'+anchor, settings[iframeId].iframe,iframeId); + }, + + sendMessage : function(message) { + message = JSON.stringify(message); + trigger('Send Message','message:'+message, settings[iframeId].iframe, iframeId); + } + }; + } + } + + //We have to call trigger twice, as we can not be sure if all + //iframes have completed loading when this code runs. The + //event listener also catches the page changing in the iFrame. + function init(msg) { + function iFrameLoaded() { + trigger('iFrame.onload', msg, iframe, undefined , true); + checkReset(); + } + + addEventListener(iframe,'load',iFrameLoaded); + trigger('init', msg, iframe, undefined, true); + } + + function checkOptions(options) { + if ('object' !== typeof options) { + throw new TypeError('Options is not an object'); + } + } + function copyOptions(options) { for (var option in defaults) { - if (defaults.hasOwnProperty(option)){ - settings[iframeID][option] = options.hasOwnProperty(option) ? options[option] : defaults[option]; + if (defaults.hasOwnProperty(option)) { + settings[iframeId][option] = options.hasOwnProperty(option) ? options[option] : defaults[option]; } } + } - logEnabled = settings[iframeID].log; + function getTargetOrigin (remoteHost) { + return ('' === remoteHost || 'file://' === remoteHost) ? '*' : remoteHost; } - var - /*jshint validthis:true */ - iframe = this, - iframeID = ensureHasId(iframe.id); - - processOptions(options); - setScrolling(); - setLimits(); - setupBodyMarginValues(); - init(createOutgoingMsg()); + function processOptions(options) { + options = options || {}; + settings[iframeId] = { + firstRun : true, + iframe : iframe, + remoteHost : iframe.src.split('/').slice(0,3).join('/') + }; + + checkOptions(options); + copyOptions(options); + + if (settings[iframeId]) { + settings[iframeId].targetOrigin = true === settings[iframeId].checkOrigin ? getTargetOrigin(settings[iframeId].remoteHost) : '*'; + } + } + + function beenHere() { + return (iframeId in settings && 'iFrameResizer' in iframe); + } + + var iframeId = ensureHasId(iframe.id); + + if (!beenHere()) { + processOptions(options); + setScrolling(); + setLimits(); + setupBodyMarginValues(); + init(createOutgoingMsg(iframeId)); + setupIFrameObject(); + } else { + warn(iframeId,'Ignored iFrame, already setup.'); + } } - function throttle(fn,time){ - if (null === timer){ - timer = setTimeout(function(){ + function debouce(fn,time) { + if (null === timer) { + timer = setTimeout(function() { timer = null; fn(); }, time); } } - function winResize(){ - throttle(function(){ - for (var iframeId in settings){ - if('parent' === settings[iframeId].resizeFrom){ - trigger('Window resize','resize',document.getElementById(iframeId),iframeId); + /* istanbul ignore next */ //Not testable in PhantomJS + function fixHiddenIFrames() { + function checkIFrames() { + function checkIFrame(settingId) { + function chkDimension(dimension) { + return '0px' === (settings[settingId] && settings[settingId].iframe.style[dimension]); + } + + function isVisible(el) { + return (null !== el.offsetParent); + } + + if (settings[settingId] && isVisible(settings[settingId].iframe) && (chkDimension('height') || chkDimension('width'))) { + trigger('Visibility change', 'resize', settings[settingId].iframe, settingId); } } - },66); + + for (var settingId in settings) { + checkIFrame(settingId); + } + } + + function mutationObserved(mutations) { + log('window','Mutation observed: ' + mutations[0].target + ' ' + mutations[0].type); + debouce(checkIFrames,16); + } + + function createMutationObserver() { + var + target = document.querySelector('body'), + + config = { + attributes : true, + attributeOldValue : false, + characterData : true, + characterDataOldValue : false, + childList : true, + subtree : true + }, + + observer = new MutationObserver(mutationObserved); + + observer.observe(target, config); + } + + var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; + + if (MutationObserver) createMutationObserver(); } - function factory(){ - setupRequestAnimationFrame(); + function resizeIFrames(event) { + function resize() { + sendTriggerMsg('Window '+event,'resize'); + } + + log('window','Trigger event: '+event); + debouce(resize,16); + } + + /* istanbul ignore next */ //Not testable in PhantomJS + function tabVisible() { + function resize() { + sendTriggerMsg('Tab Visable','resize'); + } + + if('hidden' !== document.visibilityState) { + log('document','Trigger event: Visiblity change'); + debouce(resize,16); + } + } + + function sendTriggerMsg(eventName,event) { + function isIFrameResizeEnabled(iframeId) { + return settings[iframeId] && + 'parent' === settings[iframeId].resizeFrom && + settings[iframeId].autoResize && + !settings[iframeId].firstRun; + } + + for (var iframeId in settings) { + if(isIFrameResizeEnabled(iframeId)) { + trigger(eventName, event, document.getElementById(iframeId), iframeId); + } + } + } + + function setupEventListeners() { addEventListener(window,'message',iFrameListener); - addEventListener(window,'resize', winResize); - function init(element, options){ - if(!element.tagName) { - throw new TypeError('Object is not a valid DOM element'); - } else if ('IFRAME' !== element.tagName.toUpperCase()) { - throw new TypeError('Expected