ZeroOmega/omega-web/lib/iframeResizer/iframeResizer.min.js
2024-05-24 19:03:49 +08:00

1467 lines
37 KiB
JavaScript

/*
* 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 = Object.freeze({
max: 1,
scroll: 1,
bodyScroll: 1,
documentElementScroll: 1
}),
settings = {},
timer = null,
defaults = Object.freeze({
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,
mouseEvents: true,
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')
},
onMouseEnter: function () {},
onMouseLeave: function () {},
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) {
// 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)
} else {
log('setup', 'RequestAnimationFrame not supported')
}
}
function getMyID(iframeId) {
var retStr = 'Host page: ' + iframeId
if (window.top !== window.self) {
retStr =
window.parentIFrame && window.parentIFrame.getId
? window.parentIFrame.getId() + ': ' + iframeId
: '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.slice(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).slice(0, msgIdLen) &&
msg.slice(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.
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.slice(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) {
reposition()
} else {
scrollParent()
}
}
function scrollTo() {
if (false === on('onScroll', pagePosition)) {
unsetPagePosition()
} else {
setPagePosition(iframeId)
}
}
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) {
log(iframeId, 'In page link #' + hash + ' not found')
} else {
jumpToParent()
}
}
function onMouse(event) {
var mousePos = {}
if (Number(messageData.width) === 0 && Number(messageData.height) === 0) {
var data = getMsgBody(9).split(':')
mousePos = {
x: data[1],
y: data[0]
}
} else {
mousePos = {
x: messageData.width,
y: messageData.height
}
}
on(event, {
iframe: messageData.iframe,
screenX: Number(mousePos.x),
screenY: Number(mousePos.y),
type: messageData.type
})
}
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 'mouseenter': {
onMouse('onMouseEnter')
break
}
case 'mouseleave': {
onMouse('onMouseLeave')
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: {
if (
Number(messageData.width) === 0 &&
Number(messageData.height) === 0
) {
warn(
'Unsupported message received (' +
messageData.type +
'), this is likely due to the iframe containing a later ' +
'version of iframe-resizer than the parent page'
)
} else {
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
? document.documentElement.scrollLeft
: window.pageXOffset,
y:
window.pageYOffset === undefined
? document.documentElement.scrollTop
: window.pageYOffset
}
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 +
':' +
settings[iframeId].mouseEvents
)
}
function isNumber(value) {
return typeof value === 'number'
}
function setupIFrame(iframe, options) {
function setLimits() {
function addStyle(style) {
var styleValue = settings[iframeId][style]
if (Infinity !== styleValue && 0 !== styleValue) {
iframe.style[style] = isNumber(styleValue)
? styleValue + 'px'
: styleValue
log(iframeId, 'Set ' + style + ' = ' + iframe.style[style])
}
}
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 (typeof iframeId !== 'string') {
throw new TypeError('Invaild id for iFrame. Expected String')
}
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] = Object.create(null) // Protect against prototype attacks
settings[iframeId].iframe = iframe
settings[iframeId].firstRun = true
settings[iframeId].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()) {
warn(iframeId, 'Ignored iFrame, already setup.')
} else {
processOptions(options)
setScrolling()
setLimits()
setupBodyMarginValues()
init(createOutgoingMsg(iframeId))
setupIFrameObject()
}
}
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 Visible', 'resize')
}
if ('hidden' !== document.visibilityState) {
log('document', 'Trigger event: Visibility 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 <IFRAME> tag, found <' + element.tagName + '>'
)
}
}
if (element) {
chkType()
setupIFrame(element, options)
iFrames.push(element)
}
}
function warnDeprecatedOptions(options) {
if (options && options.enablePublicMethods) {
warn(
'enablePublicMethods option has been removed, public methods are now always available in the iFrame'
)
}
}
var iFrames
setupRequestAnimationFrame()
setupEventListeners()
return function iFrameResizeF(options, target) {
iFrames = [] // Only return iFrames past in on this call
warnDeprecatedOptions(options)
switch (typeof target) {
case 'undefined':
case 'string': {
Array.prototype.forEach.call(
document.querySelectorAll(target || 'iframe'),
init.bind(undefined, options)
)
break
}
case 'object': {
init(options, target)
break
}
default: {
throw new TypeError('Unexpected data type (' + typeof target + ')')
}
}
return iFrames
}
}
function createJQueryPublicMethod($) {
if (!$.fn) {
info('', 'Unable to bind to jQuery, it is not fully loaded.')
} else if (!$.fn.iFrameResize) {
$.fn.iFrameResize = function $iFrameResizeF(options) {
function init(index, element) {
setupIFrame(element, options)
}
return this.filter('iframe').each(init).end()
}
}
}
if (window.jQuery !== undefined) {
createJQueryPublicMethod(window.jQuery)
}
if (typeof define === 'function' && define.amd) {
define([], factory)
} else if (typeof module === 'object' && typeof module.exports === 'object') {
// Node for browserfy
module.exports = factory()
}
window.iFrameResize = window.iFrameResize || factory()
})()