/* * 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 */ // eslint-disable-next-line sonarjs/cognitive-complexity, no-shadow-restricted-names ;(function (undefined) { if (typeof window === 'undefined') return // don't run for server side render var count = 0, logEnabled = false, hiddenCheckEnabled = false, msgHeader = 'message', msgHeaderLen = msgHeader.length, msgId = '[iFrameSizer]', // Must match iframe msg ID msgIdLen = msgId.length, pagePosition = null, requestAnimationFrame = window.requestAnimationFrame, resetRequiredMethods = { max: 1, scroll: 1, bodyScroll: 1, documentElementScroll: 1 }, settings = {}, timer = null, defaults = { autoResize: true, bodyBackground: null, bodyMargin: null, bodyMarginV1: 8, bodyPadding: null, checkOrigin: true, inPageLinks: false, enablePublicMethods: true, heightCalculationMethod: 'bodyOffset', id: 'iFrameResizer', interval: 32, log: false, maxHeight: Infinity, maxWidth: Infinity, minHeight: 0, minWidth: 0, resizeFrom: 'parent', scrolling: false, sizeHeight: true, sizeWidth: false, warningTimeout: 5000, tolerance: 0, widthCalculationMethod: 'scroll', onClose: function () { return true }, onClosed: function () {}, onInit: function () {}, onMessage: function () { warn('onMessage function not defined') }, onResized: function () {}, onScroll: function () { return true } } function getMutationObserver() { return ( window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver ) } function addEventListener(el, evt, func) { el.addEventListener(evt, func, false) } function removeEventListener(el, evt, func) { el.removeEventListener(evt, func, false) } function setupRequestAnimationFrame() { var vendors = ['moz', 'webkit', 'o', 'ms'] var x // Remove vendor prefixing if prefixed and break early if not for (x = 0; x < vendors.length && !requestAnimationFrame; x += 1) { requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'] } if (!requestAnimationFrame) { log('setup', 'RequestAnimationFrame not supported') } else { // Firefox extension content-scripts have a globalThis object that is not the same as window. // Binding `requestAnimationFrame` to window allows the function to work and prevents errors // being thrown when run in that context, and should be a no-op in every other context. requestAnimationFrame = requestAnimationFrame.bind(window) } } function getMyID(iframeId) { var retStr = 'Host page: ' + iframeId if (window.top !== window.self) { if (window.parentIFrame && window.parentIFrame.getId) { retStr = window.parentIFrame.getId() + ': ' + iframeId } else { retStr = 'Nested host page: ' + iframeId } } return retStr } function formatLogHeader(iframeId) { return msgId + '[' + getMyID(iframeId) + ']' } function isLogEnabled(iframeId) { return settings[iframeId] ? settings[iframeId].log : logEnabled } 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) { // eslint-disable-next-line no-console console[type](formatLogHeader(iframeId), msg) } } function iFrameListener(event) { function resizeIFrame() { function resize() { setSize(messageData) setPagePosition(iframeId) on('onResized', messageData) } ensureInRange('Height') ensureInRange('Width') syncResize(resize, messageData, 'init') } function processMsg() { var data = msg.substr(msgIdLen).split(':') var height = data[1] ? parseInt(data[1], 10) : 0 var iframe = settings[data[0]] && settings[data[0]].iframe var compStyle = getComputedStyle(iframe) return { iframe: iframe, id: data[0], height: height + getPaddingEnds(compStyle) + getBorderEnds(compStyle), width: data[2], type: data[3] } } function getPaddingEnds(compStyle) { if (compStyle.boxSizing !== 'border-box') { return 0 } var top = compStyle.paddingTop ? parseInt(compStyle.paddingTop, 10) : 0 var bot = compStyle.paddingBottom ? parseInt(compStyle.paddingBottom, 10) : 0 return top + bot } function getBorderEnds(compStyle) { if (compStyle.boxSizing !== 'border-box') { return 0 } var top = compStyle.borderTopWidth ? parseInt(compStyle.borderTopWidth, 10) : 0 var bot = compStyle.borderBottomWidth ? parseInt(compStyle.borderBottomWidth, 10) : 0 return top + bot } function ensureInRange(Dimension) { var max = Number(settings[iframeId]['max' + Dimension]), min = Number(settings[iframeId]['min' + Dimension]), dimension = Dimension.toLowerCase(), size = Number(messageData[dimension]) log(iframeId, 'Checking ' + dimension + ' is in range ' + min + '-' + max) if (size < min) { size = min log(iframeId, 'Set ' + dimension + ' to min value') } if (size > max) { size = max log(iframeId, 'Set ' + dimension + ' to max value') } messageData[dimension] = '' + size } 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) { retCode = true break } } return retCode } 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] && 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) && 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 compatibity. var retCode = messageData.type in { true: 1, false: 1, undefined: 1 } if (retCode) { log(iframeId, 'Ignoring init message from meta parent page') } return retCode } function getMsgBody(offset) { return msg.substr(msg.indexOf(':') + msgHeaderLen + offset) } function forwardMsgFromIFrame(msgBody) { log( iframeId, 'onMessage passed: {iframe: ' + messageData.iframe.id + ', message: ' + msgBody + '}' ) on('onMessage', { iframe: messageData.iframe, message: JSON.parse(msgBody) }) 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, documentHeight: document.documentElement.clientHeight, documentWidth: document.documentElement.clientWidth, windowHeight: window.innerHeight, windowWidth: window.innerWidth }) } function sendPageInfoToIframe(iframe, iframeId) { function debouncedTrigger() { trigger('Send Page Info', 'pageInfo:' + getPageInfo(), iframe, iframeId) } debounceFrameEvents(debouncedTrigger, 32, iframeId) } 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 stopPageInfoMonitor() { if (settings[iframeId] && settings[iframeId].stopPageInfo) { settings[iframeId].stopPageInfo() delete settings[iframeId].stopPageInfo } } function checkIFrameExists() { var retBool = true if (null === messageData.iframe) { warn(iframeId, 'IFrame (' + messageData.id + ') not found') retBool = false } return retBool } function getElementPosition(target) { var iFramePosition = target.getBoundingClientRect() getPagePosition(iframeId) return { x: Math.floor(Number(iFramePosition.left) + Number(pagePosition.x)), y: Math.floor(Number(iFramePosition.top) + Number(pagePosition.y)) } } function scrollRequestFromChild(addOffset) { /* istanbul ignore next */ // Not testable in Karma function reposition() { pagePosition = newPosition scrollTo() log(iframeId, '--') } 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( iframeId, 'Reposition requested from iFrame (offset x:' + offset.x + ' y:' + offset.y + ')' ) if (window.top !== window.self) { scrollParent() } else { reposition() } } function scrollTo() { if (false !== on('onScroll', pagePosition)) { setPagePosition(iframeId) } else { unsetPagePosition() } } function findTarget(location) { function jumpToTarget() { var jumpPosition = getElementPosition(target) log( iframeId, 'Moving to in page link (#' + hash + ') at x: ' + jumpPosition.x + ' y: ' + jumpPosition.y ) pagePosition = { x: jumpPosition.x, y: jumpPosition.y } scrollTo() log(iframeId, '--') } function jumpToParent() { if (window.parentIFrame) { window.parentIFrame.moveToAnchor(hash) } else { log( iframeId, 'In page link #' + hash + ' not found and window.parentIFrame not found' ) } } 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(iframeId, 'In page link #' + hash + ' not found') } } function on(funcName, val) { return chkEvent(iframeId, funcName, val) } function actionMsg() { if (settings[iframeId] && settings[iframeId].firstRun) firstRun() switch (messageData.type) { case 'close': closeIFrame(messageData.iframe) break case 'message': forwardMsgFromIFrame(getMsgBody(6)) break case 'autoResize': settings[iframeId].autoResize = JSON.parse(getMsgBody(9)) 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() on('onInit', messageData.iframe) break default: resizeIFrame() } } function hasSettings(iframeId) { var retBool = true if (!settings[iframeId]) { retBool = false warn( messageData.type + ' No settings for ' + iframeId + '. Message was: ' + msg ) } return retBool } function iFrameReadyMsgReceived() { // eslint-disable-next-line no-restricted-syntax, guard-for-in for (var iframeId in settings) { trigger( 'iFrame requested init', createOutgoingMsg(iframeId), settings[iframeId].iframe, iframeId ) } } function firstRun() { if (settings[iframeId]) { settings[iframeId].firstRun = false } } var msg = event.data, messageData = {}, iframeId = null if ('[iFrameResizerChild]Ready' === msg) { iFrameReadyMsgReceived() } else if (isMessageForUs()) { messageData = processMsg() iframeId = messageData.id if (settings[iframeId]) { settings[iframeId].loaded = true } if (!isMessageFromMetaParent() && hasSettings(iframeId)) { log(iframeId, 'Received: ' + msg) if (checkIFrameExists() && isMessageFromIFrame()) { actionMsg() } } } else { info(iframeId, 'Ignored: ' + msg) } } function chkEvent(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 removeIframeListeners(iframe) { var iframeId = iframe.id delete settings[iframeId] } function closeIFrame(iframe) { var iframeId = iframe.id if (chkEvent(iframeId, 'onClose', iframeId) === false) { log(iframeId, 'Close iframe cancelled by onClose event') return } log(iframeId, 'Removing iFrame: ' + iframeId) try { // Catch race condition error with React if (iframe.parentNode) { iframe.parentNode.removeChild(iframe) } } catch (error) { warn(error) } chkEvent(iframeId, 'onClosed', iframeId) log(iframeId, '--') removeIframeListeners(iframe) } 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( iframeId, 'Get page position: ' + pagePosition.x + ',' + pagePosition.y ) } } function setPagePosition(iframeId) { if (null !== pagePosition) { window.scrollTo(pagePosition.x, pagePosition.y) log( iframeId, 'Set page position: ' + pagePosition.x + ',' + pagePosition.y ) unsetPagePosition() } } function unsetPagePosition() { pagePosition = null } function resetIFrame(messageData) { function reset() { setSize(messageData) trigger('reset', 'reset', messageData.iframe, messageData.id) } 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) { if (!messageData.id) { log('undefined', 'messageData id not set') return } messageData.iframe.style[dimension] = messageData[dimension] + 'px' log( messageData.id, 'IFrame (' + iframeId + ') ' + dimension + ' set to ' + messageData[dimension] + 'px' ) } 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) { /* istanbul ignore if */ // Not testable in PhantomJS if ( doNotSync !== messageData.type && requestAnimationFrame && // including check for jasmine because had trouble getting spy to work in unit test using requestAnimationFrame !window.jasmine ) { log(messageData.id, 'Requesting animation frame') requestAnimationFrame(func) } else { func() } } 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 ignored 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 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 newId() { var id = (options && options.id) || defaults.id + count++ if (null !== document.getElementById(id)) { id += count++ } return id } function ensureHasId(iframeId) { if ('' === iframeId) { // eslint-disable-next-line no-multi-assign iframe.id = iframeId = newId() logEnabled = (options || {}).log log( iframeId, 'Added missing iframe ID: ' + iframeId + ' (' + iframe.src + ')' ) } return iframeId } 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 'omit': break 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] && settings[iframeId].bodyMargin) || '0' === (settings[iframeId] && settings[iframeId].bodyMargin) ) { settings[iframeId].bodyMarginV1 = settings[iframeId].bodyMargin settings[iframeId].bodyMargin = '' + settings[iframeId].bodyMargin + 'px' } } 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 setupIFrameObject() { if (settings[iframeId]) { settings[iframeId].iframe.iFrameResizer = { close: closeIFrame.bind(null, settings[iframeId].iframe), removeListeners: removeIframeListeners.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() } function createDestroyObserver(MutationObserver) { if (!iframe.parentNode) { return } var destroyObserver = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { var removedNodes = Array.prototype.slice.call(mutation.removedNodes) // Transform NodeList into an Array removedNodes.forEach(function (removedNode) { if (removedNode === iframe) { closeIFrame(iframe) } }) }) }) destroyObserver.observe(iframe.parentNode, { childList: true }) } var MutationObserver = getMutationObserver() if (MutationObserver) { createDestroyObserver(MutationObserver) } 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) { // eslint-disable-next-line no-restricted-syntax for (var option in defaults) { if (Object.prototype.hasOwnProperty.call(defaults, option)) { settings[iframeId][option] = Object.prototype.hasOwnProperty.call( options, option ) ? options[option] : defaults[option] } } } function getTargetOrigin(remoteHost) { return '' === remoteHost || null !== remoteHost.match(/^(about:blank|javascript:|file:\/\/)/) ? '*' : remoteHost } function depricate(key) { var splitName = key.split('Callback') if (splitName.length === 2) { var name = 'on' + splitName[0].charAt(0).toUpperCase() + splitName[0].slice(1) this[name] = this[key] delete this[key] warn( iframeId, "Deprecated: '" + key + "' has been renamed '" + name + "'. The old method will be removed in the next major version." ) } } function processOptions(options) { options = options || {} settings[iframeId] = { firstRun: true, iframe: iframe, remoteHost: iframe.src && iframe.src.split('/').slice(0, 3).join('/') } checkOptions(options) Object.keys(options).forEach(depricate, 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 debouce(fn, time) { if (null === timer) { timer = setTimeout(function () { timer = null fn() }, time) } } var frameTimer = {} function debounceFrameEvents(fn, time, frameId) { if (!frameTimer[frameId]) { frameTimer[frameId] = setTimeout(function () { frameTimer[frameId] = null fn() }, time) } } // Not testable in PhantomJS /* istanbul ignore next */ 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 ) } } Object.keys(settings).forEach(function (key) { checkIFrame(key) }) } 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 = getMutationObserver() if (MutationObserver) { createMutationObserver() } } function resizeIFrames(event) { function resize() { sendTriggerMsg('Window ' + event, 'resize') } log('window', 'Trigger event: ' + event) debouce(resize, 16) } // Not testable in PhantomJS /* istanbul ignore next */ 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 ) } Object.keys(settings).forEach(function (iframeId) { if (isIFrameResizeEnabled(iframeId)) { trigger(eventName, event, settings[iframeId].iframe, iframeId) } }) } function setupEventListeners() { addEventListener(window, 'message', iFrameListener) addEventListener(window, 'resize', function () { resizeIFrames('resize') }) addEventListener(document, 'visibilitychange', tabVisible) addEventListener(document, '-webkit-visibilitychange', tabVisible) } function factory() { function init(options, element) { function chkType() { if (!element.tagName) { throw new TypeError('Object is not a valid DOM element') } else if ('IFRAME' !== element.tagName.toUpperCase()) { throw new TypeError( 'Expected