diff --git a/LICENSE.txt b/LICENSE.txt index 1e94e57b2b7320e099d72a3911c5270ae196d878..60ab3ac6f115e10957efeb150d5a1a87837245d1 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -316,6 +316,10 @@ Applications: See licenses/LICENSE-NDT.txt Notice: I2P has changed specified portions of the Software, including the package edu.internet2.ndt. + Router Console Iframe-resizer 4.3.9: + Copyright (c) 2013-2023 David J. Bradshaw + See licenses/LICENSE-Iframe-resizer.txt + SAM (sam.jar): Public domain. diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 35ec8d23c9d7ae63f25f9c6afaef5ee4909cb173..d13ecb362d3b6e41100fb5c36c8cf1cf703508ce 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -348,6 +348,7 @@ public class I2PSnarkServlet extends BasicServlet { "<script src=\".resources/js/delete.js?" + CoreVersion.VERSION + "\" type=\"text/javascript\"></script>\n" + "<script src=\".resources/js/search.js?" + CoreVersion.VERSION + "\" type=\"text/javascript\"></script>\n"); } + out.write("<script src=\"/js/iframeResizer.contentWindow.js?" + CoreVersion.VERSION + "\" type=\"text/javascript\"></script>\n"); out.write(HEADER_A + _themePath + HEADER_B); // ...and inject CSS to display panels uncollapsed diff --git a/apps/i2ptunnel/jsp/edit.jsp b/apps/i2ptunnel/jsp/edit.jsp index c23401891f4a6f0b788496802c3bd04158d55494..dba8f86c0034636ef8630e3d1e15de0f5a77c6e4 100644 --- a/apps/i2ptunnel/jsp/edit.jsp +++ b/apps/i2ptunnel/jsp/edit.jsp @@ -44,6 +44,7 @@ if (tun != null) { input.default { width: 1px; height: 1px; visibility: hidden; } </style> <script src="/js/resetScroll.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> +<script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <script src="js/tableSlider.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <script nonce="<%=cspNonce%>" type="text/javascript"> var deleteMessage = "<%=intl._t("Are you sure you want to delete?")%>"; diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp index 39b38ba3ab389022f69584da20c38044569b27b3..710cc292be29cc4c5597bdad7888d0938162836d 100644 --- a/apps/i2ptunnel/jsp/index.jsp +++ b/apps/i2ptunnel/jsp/index.jsp @@ -14,6 +14,7 @@ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" /> <link href="<%=indexBean.getTheme()%>i2ptunnel.css?<%=net.i2p.CoreVersion.VERSION%>" rel="stylesheet" type="text/css" /> + <script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <script src="js/copy.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <noscript><style> .jsonly { display: none } </style></noscript> </head><body id="tunnelListPage"> diff --git a/apps/i2ptunnel/jsp/register.jsp b/apps/i2ptunnel/jsp/register.jsp index 3051bb0cddb924f50a6d1066771f108bc1a6c938..18f4083a91cee7453add521062bfcaed674316b1 100644 --- a/apps/i2ptunnel/jsp/register.jsp +++ b/apps/i2ptunnel/jsp/register.jsp @@ -28,6 +28,7 @@ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" /> <link href="<%=editBean.getTheme()%>i2ptunnel.css?<%=net.i2p.CoreVersion.VERSION%>" rel="stylesheet" type="text/css" /> + <script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <style type='text/css'> input.default { width: 1px; height: 1px; visibility: hidden; } </style> diff --git a/apps/i2ptunnel/jsp/ssl.jsp b/apps/i2ptunnel/jsp/ssl.jsp index 1b91e4a0015ea587ee3dbc0194d5004841088eb9..b60d5138029cabb139fbb62d319aef5d6ad849bf 100644 --- a/apps/i2ptunnel/jsp/ssl.jsp +++ b/apps/i2ptunnel/jsp/ssl.jsp @@ -26,6 +26,7 @@ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" /> <link href="<%=editBean.getTheme()%>i2ptunnel.css?<%=net.i2p.CoreVersion.VERSION%>" rel="stylesheet" type="text/css" /> + <script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <style type='text/css'> input.default { width: 1px; height: 1px; visibility: hidden; } </style> diff --git a/apps/i2ptunnel/jsp/wizard.jsp b/apps/i2ptunnel/jsp/wizard.jsp index 7640243e31fd736b2c8b7a644bac5853c5e08bc5..d054ff193802dcc43b5cd594c0d9ca34c349674a 100644 --- a/apps/i2ptunnel/jsp/wizard.jsp +++ b/apps/i2ptunnel/jsp/wizard.jsp @@ -46,6 +46,7 @@ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link href="/themes/console/images/favicon.ico" type="image/x-icon" rel="shortcut icon" /> <link href="<%=editBean.getTheme()%>i2ptunnel.css?<%=net.i2p.CoreVersion.VERSION%>" rel="stylesheet" type="text/css" /> + <script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> </head> <body id="tunnelWizardPage"> <form method="post" action="<%=(curPage == 7 ? "list" : "wizard") %>"> diff --git a/apps/routerconsole/jsp/dns.jsp b/apps/routerconsole/jsp/dns.jsp index 1c717f16b9890840cd0b3a7e8a5a7cdbf69a0b6f..062c715de4912ce4ea1a7e2b7fe76b7eefa1c690 100644 --- a/apps/routerconsole/jsp/dns.jsp +++ b/apps/routerconsole/jsp/dns.jsp @@ -25,6 +25,7 @@ <%@include file="css.jsi" %> <%=intl.title("Address Book")%> <script src="/js/iframed.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> +<script src="/js/iframeResizer.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <%@include file="summaryajax.jsi" %> <script nonce="<%=cspNonce%>" type="text/javascript"> /* @license http://creativecommons.org/publicdomain/zero/1.0/legalcode CC0-1.0 */ @@ -34,6 +35,7 @@ f.addEventListener("load", function() { injectClass(f); resizeFrame(f); + iFrameResize({ log: false }, '#susidnsframe') }, true); } diff --git a/apps/routerconsole/jsp/i2ptunnelmgr.jsp b/apps/routerconsole/jsp/i2ptunnelmgr.jsp index dd2969f3aa67aa5ba8447241842ff4e91a88fc28..779ce7ed8e9d407e9877b94661f6da77c939988f 100644 --- a/apps/routerconsole/jsp/i2ptunnelmgr.jsp +++ b/apps/routerconsole/jsp/i2ptunnelmgr.jsp @@ -25,6 +25,7 @@ <%@include file="css.jsi" %> <%=intl.title("Hidden Services Manager")%> <script src="/js/iframed.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> +<script src="/js/iframeResizer.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <%@include file="summaryajax.jsi" %> <script nonce="<%=cspNonce%>" type="text/javascript"> /* @license http://creativecommons.org/publicdomain/zero/1.0/legalcode CC0-1.0 */ @@ -56,6 +57,7 @@ injectClass(f); injectClassSpecific(f); resizeFrame(f); + iFrameResize({ log: false }, '#i2ptunnelframe') }, true); } diff --git a/apps/routerconsole/jsp/js/iframeResizer.contentWindow.js b/apps/routerconsole/jsp/js/iframeResizer.contentWindow.js new file mode 100644 index 0000000000000000000000000000000000000000..d32844ef35724f44e302df706c0b2a499ff188f4 --- /dev/null +++ b/apps/routerconsole/jsp/js/iframeResizer.contentWindow.js @@ -0,0 +1,1305 @@ +/* + * File: iframeResizer.contentWindow.js + * Desc: Include this file in any page being loaded into an iframe + * to force the iframe to resize to the content size. + * Requires: iframeResizer.js on host page. + * Doc: https://github.com/davidjbradshaw/iframe-resizer + * Author: David J. Bradshaw - dave@bradshaw.net + * + */ + +// 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 autoResize = true, + base = 10, + bodyBackground = '', + bodyMargin = 0, + bodyMarginStr = '', + bodyObserver = null, + bodyPadding = '', + calculateWidth = false, + doubleEventList = { resize: 1, click: 1 }, + eventCancelTimer = 128, + firstRun = true, + height = 1, + heightCalcModeDefault = 'bodyOffset', + heightCalcMode = heightCalcModeDefault, + initLock = true, + initMsg = '', + inPageLinks = {}, + interval = 32, + intervalTimer = null, + logging = false, + mouseEvents = false, + msgID = '[iFrameSizer]', // Must match host page msg ID + msgIdLen = msgID.length, + myID = '', + resetRequiredMethods = { + max: 1, + min: 1, + bodyScroll: 1, + documentElementScroll: 1 + }, + resizeFrom = 'child', + sendPermit = true, + target = window.parent, + targetOriginDefault = '*', + tolerance = 0, + triggerLocked = false, + triggerLockedTimer = null, + throttledTimer = 16, + width = 1, + widthCalcModeDefault = 'scroll', + widthCalcMode = widthCalcModeDefault, + win = window, + onMessage = function () { + warn('onMessage function not defined') + }, + onReady = function () {}, + onPageInfo = 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 = {}, + passiveSupported = false + + function noop() {} + + try { + var options = Object.create( + {}, + { + passive: { + // eslint-disable-next-line getter-return + get: function () { + passiveSupported = true + } + } + } + ) + window.addEventListener('test', noop, options) + window.removeEventListener('test', noop, options) + } catch (error) { + /* */ + } + + function addEventListener(el, evt, func, options) { + el.addEventListener(evt, func, passiveSupported ? options || {} : false) + } + + function removeEventListener(el, evt, func) { + el.removeEventListener(evt, func, false) + } + + 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 = Date.now() + timeout = null + result = func.apply(context, args) + if (!timeout) { + // eslint-disable-next-line no-multi-assign + context = args = null + } + } + + return function () { + var now = Date.now() + + 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) { + // eslint-disable-next-line no-multi-assign + context = args = null + } + } else if (!timeout) { + timeout = setTimeout(later, remaining) + } + + return result + } + } + + function formatLogMsg(msg) { + return msgID + '[' + myID + '] ' + msg + } + + function log(msg) { + if (logging && 'object' === typeof window.console) { + // eslint-disable-next-line no-console + console.log(formatLogMsg(msg)) + } + } + + function warn(msg) { + if ('object' === typeof window.console) { + // eslint-disable-next-line no-console + console.warn(formatLogMsg(msg)) + } + } + + function init() { + readDataFromParent() + log('Initialising iFrame (' + window.location.href + ')') + readDataFromPage() + setMargin() + setBodyStyle('background', bodyBackground) + setBodyStyle('padding', bodyPadding) + injectClearFixIntoBodyElement() + checkHeightMode() + checkWidthMode() + stopInfiniteResizingOfIFrame() + setupPublicMethods() + setupMouseEvents() + startEventListeners() + inPageLinks = setupInPageLinks() + sendSize('init', 'Init message from host page') + onReady() + } + + function readDataFromParent() { + function strBool(str) { + return 'true' === str + } + + var data = initMsg.slice(msgIdLen).split(':') + + myID = data[0] + bodyMargin = undefined === data[1] ? bodyMargin : Number(data[1]) // For V1 compatibility + calculateWidth = undefined === data[2] ? calculateWidth : strBool(data[2]) + logging = undefined === data[3] ? logging : strBool(data[3]) + interval = undefined === data[4] ? interval : Number(data[4]) + autoResize = undefined === data[6] ? autoResize : strBool(data[6]) + bodyMarginStr = data[7] + heightCalcMode = undefined === data[8] ? heightCalcMode : data[8] + bodyBackground = data[9] + bodyPadding = data[10] + tolerance = undefined === data[11] ? tolerance : Number(data[11]) + inPageLinks.enable = undefined === data[12] ? false : strBool(data[12]) + resizeFrom = undefined === data[13] ? resizeFrom : data[13] + widthCalcMode = undefined === data[14] ? widthCalcMode : data[14] + mouseEvents = undefined === data[15] ? mouseEvents : strBool(data[15]) + } + + 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( + "Deprecated: '" + + key + + "' has been renamed '" + + name + + "'. The old method will be removed in the next major version." + ) + } + } + + function readDataFromPage() { + function readData() { + var data = window.iFrameResizer + + log('Reading data from page: ' + JSON.stringify(data)) + Object.keys(data).forEach(depricate, data) + + onMessage = 'onMessage' in data ? data.onMessage : onMessage + onReady = 'onReady' in data ? data.onReady : onReady + 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('-')) { + warn('Negative CSS value ignored for ' + attr) + value = '' + } + return value + } + + function setBodyStyle(attr, value) { + if (undefined !== value && '' !== value && 'null' !== value) { + document.body.style[attr] = value + log('Body ' + attr + ' set to "' + value + '"') + } + } + + function setMargin() { + // If called via V1 script, convert bodyMargin from int to str + if (undefined === bodyMarginStr) { + bodyMarginStr = bodyMargin + 'px' + } + + setBodyStyle('margin', chkCSS('margin', bodyMarginStr)) + } + + function stopInfiniteResizingOfIFrame() { + document.documentElement.style.height = '' + document.body.style.height = '' + log('HTML & body height set to "auto"') + } + + function manageTriggerEvent(options) { + var listener = { + add: function (eventName) { + function handleEvent() { + sendSize(options.eventName, options.eventType) + } + + eventHandlersByName[eventName] = handleEvent + + addEventListener(window, eventName, handleEvent, { passive: true }) + }, + 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(listener[options.method]) + } else { + listener[options.method](options.eventName) + } + + log( + capitalizeFirstLetter(options.method) + + ' event listener: ' + + options.eventType + ) + } + + 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', + eventNames: ['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 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(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() { + if (true === autoResize) { + manageEventListeners('add') + setupMutationObserver() + } else { + log('Auto Resize disabled') + } + } + + // 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' + // Guard against the following having been globally redefined in CSS. + clearFix.style.display = 'block' + clearFix.style.height = '0' + document.body.appendChild(clearFix) + } + + function setupInPageLinks() { + function getPagePosition() { + return { + x: + window.pageXOffset === undefined + ? document.documentElement.scrollLeft + : window.pageXOffset, + y: + window.pageYOffset === undefined + ? document.documentElement.scrollTop + : window.pageYOffset + } + } + + function getElementPosition(el) { + var elPosition = el.getBoundingClientRect(), + pagePosition = getPagePosition() + + return { + x: parseInt(elPosition.left, 10) + parseInt(pagePosition.x, 10), + y: parseInt(elPosition.top, 10) + parseInt(pagePosition.y, 10) + } + } + + 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 hash = location.split('#')[1] || location, // Remove # if present + hashData = decodeURIComponent(hash), + target = + document.getElementById(hashData) || + document.getElementsByName(hashData)[0] + + if (undefined === target) { + log( + 'In page link (#' + + hash + + ') not found in iFrame, so sending to parent' + ) + sendMsg(0, 0, 'inPageLink', '#' + hash) + } else { + jumpToTarget(target) + } + } + + function checkLocationHash() { + var hash = window.location.hash + var href = window.location.href + + if ('' !== hash && '#' !== hash) { + findTarget(href) + } + } + + function bindAnchors() { + function setupLink(el) { + function linkClicked(e) { + e.preventDefault() + + /* jshint validthis:true */ + findTarget(this.getAttribute('href')) + } + + if ('#' !== el.getAttribute('href')) { + addEventListener(el, 'click', linkClicked) + } + } + + Array.prototype.forEach.call( + document.querySelectorAll('a[href^="#"]'), + setupLink + ) + } + + function bindLocationHash() { + addEventListener(window, 'hashchange', checkLocationHash) + } + + function initCheck() { + // Check if page loaded with location hash after init resize + setTimeout(checkLocationHash, eventCancelTimer) + } + + function enableInPageLinks() { + /* istanbul ignore else */ // Not testable in phantonJS + if (Array.prototype.forEach && document.querySelectorAll) { + log('Setting up location.hash handlers') + bindAnchors() + bindLocationHash() + initCheck() + } else { + warn( + 'In page linking not fully supported in this browser! (See README.md for IE8 workaround)' + ) + } + } + + if (inPageLinks.enable) { + enableInPageLinks() + } else { + log('In page linking not enabled') + } + + return { + findTarget: findTarget + } + } + + function setupMouseEvents() { + if (mouseEvents !== true) return + + function sendMouse(e) { + sendMsg(0, 0, e.type, e.screenY + ':' + e.screenX) + } + + function addMouseListener(evt, name) { + log('Add event listener: ' + name) + addEventListener(window.document, evt, sendMouse) + } + + addMouseListener('mouseenter', 'Mouse Enter') + addMouseListener('mouseleave', 'Mouse Leave') + } + + function setupPublicMethods() { + log('Enable public methods') + + win.parentIFrame = { + autoResize: function autoResizeF(resize) { + if (true === resize && false === autoResize) { + autoResize = true + startEventListeners() + } else if (false === resize && true === autoResize) { + autoResize = false + stopEventListeners() + } + sendMsg(0, 0, 'autoResize', JSON.stringify(autoResize)) + return autoResize + }, + + close: function closeF() { + sendMsg(0, 0, 'close') + // teardown() + }, + + getId: function getIdF() { + return myID + }, + + getPageInfo: function getPageInfoF(callback) { + if ('function' === typeof callback) { + onPageInfo = callback + sendMsg(0, 0, 'pageInfo') + } else { + onPageInfo = 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 || '') + (customWidth ? ',' + customWidth : '') + sendSize( + 'size', + 'parentIFrame.size(' + valString + ')', + customHeight, + customWidth + ) + } + } + } + + function initInterval() { + if (0 !== interval) { + log('setInterval: ' + interval + 'ms') + intervalTimer = setInterval(function () { + sendSize('interval', 'setInterval: ' + interval) + }, Math.abs(interval)) + } + } + + // Not testable in PhantomJS + /* istanbul ignore next */ + 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) + } + } + + 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) + } + + function imageLoaded(event) { + imageEventTriggered(event, 'imageLoad', 'Image loaded') + } + + function imageError(event) { + imageEventTriggered(event, 'imageLoadFailed', 'Image load failed') + } + + function mutationObserved(mutations) { + sendSize( + 'mutationObserver', + 'mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type + ) + + // Deal with WebKit / Blink asyncing image loading when tags are injected into the page + mutations.forEach(addImageLoadListners) + } + + function createMutationObserver() { + var target = document.querySelector('body'), + config = { + attributes: true, + attributeOldValue: false, + characterData: true, + characterDataOldValue: false, + childList: true, + subtree: true + } + + observer = new MutationObserver(mutationObserved) + + log('Create body MutationObserver') + observer.observe(target, config) + + return observer + } + + 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 + + // Not testable in PhantomJS + /* istanbul ignore if */ if ( + window.MutationObserver || + window.WebKitMutationObserver + ) { + if (forceIntervalTimer) { + initInterval() + } else { + bodyObserver = setupBodyMutationObserver() + } + } else { + log('MutationObserver not supported in this browser!') + initInterval() + } + } + + // document.documentElement.offsetHeight is not reliable, so + // we have to jump through hoops to get a better value. + function getComputedStyle(prop, el) { + var retVal = 0 + el = el || document.body // Not testable in phantonJS + + retVal = document.defaultView.getComputedStyle(el, null) + retVal = null === retVal ? 0 : retVal[prop] + + return parseInt(retVal, base) + } + + function chkEventThottle(timer) { + if (timer > throttledTimer / 2) { + throttledTimer = 2 * timer + log('Event throttle increased to ' + throttledTimer + 'ms') + } + } + + // Idea from https://github.com/guardian/iframe-messenger + function getMaxElement(side, elements) { + var elementsLength = elements.length, + elVal = 0, + maxVal = 0, + Side = capitalizeFirstLetter(side), + timer = Date.now() + + for (var i = 0; i < elementsLength; i++) { + elVal = + elements[i].getBoundingClientRect()[side] + + getComputedStyle('margin' + Side, elements[i]) + if (elVal > maxVal) { + maxVal = elVal + } + } + + timer = Date.now() - timer + + log('Parsed ' + elementsLength + ' HTML elements') + log('Element position calculated in ' + timer + 'ms') + + chkEventThottle(timer) + + return maxVal + } + + function getAllMeasurements(dimensions) { + return [ + dimensions.bodyOffset(), + dimensions.bodyScroll(), + dimensions.documentElementOffset(), + dimensions.documentElementScroll() + ] + } + + function getTaggedElements(side, tag) { + function noTaggedElementsFound() { + warn('No tagged elements (' + tag + ') found on page') + return document.querySelectorAll('body *') + } + + var elements = document.querySelectorAll('[' + tag + ']') + + if (elements.length === 0) noTaggedElementsFound() + + return getMaxElement(side, elements) + } + + function getAllElements() { + return document.querySelectorAll('body *') + } + + var getHeight = { + bodyOffset: function getBodyOffsetHeight() { + return ( + document.body.offsetHeight + + getComputedStyle('marginTop') + + getComputedStyle('marginBottom') + ) + }, + + offset: function () { + return getHeight.bodyOffset() // Backwards compatibility + }, + + bodyScroll: function getBodyScrollHeight() { + return document.body.scrollHeight + }, + + custom: function getCustomWidth() { + return customCalcMethods.height() + }, + + 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() || getHeight.documentElementOffset(), + 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()) + }, + + 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 isSizeChangeDetected() { + function checkTolarance(a, b) { + var retVal = Math.abs(a - b) <= tolerance + return !retVal + } + + currentHeight = + undefined === customHeight ? getHeight[heightCalcMode]() : customHeight + currentWidth = + undefined === customWidth ? getWidth[widthCalcMode]() : customWidth + + return ( + checkTolarance(height, currentHeight) || + (calculateWidth && checkTolarance(width, currentWidth)) + ) + } + + function isForceResizableEvent() { + return !(triggerEvent in { init: 1, interval: 1, size: 1 }) + } + + function isForceResizableCalcMode() { + return ( + heightCalcMode in resetRequiredMethods || + (calculateWidth && widthCalcMode in resetRequiredMethods) + ) + } + + function logIgnored() { + log('No change in size detected') + } + + function checkDownSizing() { + if (isForceResizableEvent() && isForceResizableCalcMode()) { + resetIFrame(triggerEventDesc) + } else if (!(triggerEvent in { interval: 1 })) { + logIgnored() + } + } + + 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()) { + log('Trigger event cancelled: ' + triggerEvent) + } else { + recordTrigger() + if (triggerEvent === 'init') { + sizeIFrame(triggerEvent, triggerEventDesc, customHeight, customWidth) + } else { + sizeIFrameThrottled( + triggerEvent, + triggerEventDesc, + customHeight, + customWidth + ) + } + } + } + + function lockTrigger() { + if (!triggerLocked) { + triggerLocked = true + log('Trigger event lock on') + } + clearTimeout(triggerLockedTimer) + triggerLockedTimer = setTimeout(function () { + triggerLocked = false + log('Trigger event lock off') + log('--') + }, eventCancelTimer) + } + + function triggerReset(triggerEvent) { + height = getHeight[heightCalcMode]() + width = getWidth[widthCalcMode]() + + sendMsg(height, width, triggerEvent) + } + + function resetIFrame(triggerEventDesc) { + var hcm = heightCalcMode + heightCalcMode = heightCalcModeDefault + + log('Reset trigger event: ' + triggerEventDesc) + lockTrigger() + triggerReset('reset') + + heightCalcMode = hcm + } + + function sendMsg(height, width, triggerEvent, msg, targetOrigin) { + function setTargetOrigin() { + if (undefined === targetOrigin) { + targetOrigin = targetOriginDefault + } else { + log('Message targetOrigin: ' + targetOrigin) + } + } + + function sendToParent() { + var size = height + ':' + width, + message = + myID + + ':' + + size + + ':' + + triggerEvent + + (undefined === msg ? '' : ':' + msg) + + log('Sending message to host page (' + message + ')') + target.postMessage(msgID + message, targetOrigin) + } + + if (true === sendPermit) { + setTargetOrigin() + sendToParent() + } + } + + function receiver(event) { + var processRequestFromParent = { + init: function initFromParent() { + initMsg = event.data + target = event.source + + init() + firstRun = false + setTimeout(function () { + initLock = false + }, eventCancelTimer) + }, + + reset: function resetFromParent() { + if (initLock) { + log('Page reset ignored by init') + } else { + log('Page size reset by host page') + triggerReset('resetPage') + } + }, + + resize: function resizeFromParent() { + sendSize('resizeParent', 'Parent window requested size check') + }, + + moveToAnchor: function moveToAnchorF() { + inPageLinks.findTarget(getData()) + }, + inPageLink: function inPageLinkF() { + this.moveToAnchor() + }, // Backward compatibility + + pageInfo: function pageInfoFromParent() { + var msgBody = getData() + log('PageInfoFromParent called from parent: ' + msgBody) + onPageInfo(JSON.parse(msgBody)) + log(' --') + }, + + message: function messageFromParent() { + var msgBody = getData() + + log('onMessage called from parent: ' + msgBody) + // eslint-disable-next-line sonarjs/no-extra-arguments + onMessage(JSON.parse(msgBody)) + log(' --') + } + } + + function isMessageForUs() { + return msgID === ('' + event.data).slice(0, msgIdLen) // ''+ Protects against non-string messages + } + + function getMessageType() { + return event.data.split(']')[1].split(':')[0] + } + + function getData() { + return event.data.slice(event.data.indexOf(':') + 1) + } + + function isMiddleTier() { + return ( + (!(typeof module !== 'undefined' && module.exports) && + 'iFrameResize' in window) || + (window.jQuery !== undefined && + 'iFrameResize' in window.jQuery.prototype) + ) + } + + 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 + ')') + } + } + + function processMessage() { + if (false === firstRun) { + callFromParent() + } else if (isInitMsg()) { + processRequestFromParent.init() + } else { + 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) + addEventListener(window, 'readystatechange', chkLateLoaded) + chkLateLoaded() + + +})() diff --git a/apps/routerconsole/jsp/js/iframeResizer.js b/apps/routerconsole/jsp/js/iframeResizer.js new file mode 100644 index 0000000000000000000000000000000000000000..4ea1d309a94a9ca1bafe0a5dbcf713f0496167c8 --- /dev/null +++ b/apps/routerconsole/jsp/js/iframeResizer.js @@ -0,0 +1,1466 @@ +/* + * 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() +})() diff --git a/apps/routerconsole/jsp/js/iframed.js b/apps/routerconsole/jsp/js/iframed.js index 58fe8bbdcbe51f15aa0bf73ee9c7d90f481fdd6b..f4df680b6f1046ae0cf9061341b09e3a9e86a3f9 100644 --- a/apps/routerconsole/jsp/js/iframed.js +++ b/apps/routerconsole/jsp/js/iframed.js @@ -38,6 +38,9 @@ function resizeFrame(f) { // Delete the div document.body.removeChild(scrollDiv); + + // a little extra just in case there's some margin in the iframe + totalHeight += 20; } f.style.height = totalHeight + "px"; diff --git a/apps/routerconsole/jsp/torrents.jsp b/apps/routerconsole/jsp/torrents.jsp index 1d9f46f5fbee16ad94b5a32ee090c08b948afbb9..dce6c7022b720faea9ea860e4c594dd51515bb05 100644 --- a/apps/routerconsole/jsp/torrents.jsp +++ b/apps/routerconsole/jsp/torrents.jsp @@ -26,6 +26,7 @@ <%@include file="css.jsi" %> <%=intl.title("torrents")%> <script src="/js/iframed.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> +<script src="/js/iframeResizer.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <%@include file="summaryajax.jsi" %> <script nonce="<%=cspNonce%>" type="text/javascript"> /* @license http://creativecommons.org/publicdomain/zero/1.0/legalcode CC0-1.0 */ @@ -35,6 +36,7 @@ f.addEventListener("load", function() { injectClass(f); resizeFrame(f); + iFrameResize({ log: false }, '#i2psnarkframe') }, true); } diff --git a/apps/routerconsole/jsp/webmail.jsp b/apps/routerconsole/jsp/webmail.jsp index 889b9e152f5090c804e4c539651bddd559839c6f..598b02852b22c1d523e59801651c8f7b0d8c580f 100644 --- a/apps/routerconsole/jsp/webmail.jsp +++ b/apps/routerconsole/jsp/webmail.jsp @@ -26,6 +26,7 @@ <%@include file="css.jsi" %> <%=intl.title("webmail")%> <script src="/js/iframed.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> +<script src="/js/iframeResizer.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <%@include file="summaryajax.jsi" %> <script nonce="<%=cspNonce%>" type="text/javascript"> /* @license http://creativecommons.org/publicdomain/zero/1.0/legalcode CC0-1.0 */ @@ -35,6 +36,7 @@ f.addEventListener("load", function() { injectClass(f); resizeFrame(f); + iFrameResize({ log: false }, '#susimailframe') }, true); } diff --git a/apps/susidns/src/jsp/addressbook.jsp b/apps/susidns/src/jsp/addressbook.jsp index eec385a3ea5eb7f9e9eea1363c09398dfd27d51e..cb54d92e24bb8c27733c068fe4a1956610738f3b 100644 --- a/apps/susidns/src/jsp/addressbook.jsp +++ b/apps/susidns/src/jsp/addressbook.jsp @@ -58,6 +58,7 @@ <title>${book.book} <%=intl._t("address book")%> - susidns</title> <link rel="stylesheet" type="text/css" href="<%=book.getTheme()%>susidns.css?<%=net.i2p.CoreVersion.VERSION%>"> <script src="/js/resetScroll.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> +<script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <script src="js/messages.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> </head> <body> diff --git a/apps/susidns/src/jsp/config.jsp b/apps/susidns/src/jsp/config.jsp index 8f788feaaaa429f23adc60e74dad0c4eb570ba23..ee722338ce49d25d20b2b9622f4edb52dcfe87e5 100644 --- a/apps/susidns/src/jsp/config.jsp +++ b/apps/susidns/src/jsp/config.jsp @@ -37,6 +37,7 @@ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title><%=intl._t("configuration")%> - susidns</title> <link rel="stylesheet" type="text/css" href="<%=base.getTheme()%>susidns.css?<%=net.i2p.CoreVersion.VERSION%>"> +<script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <script src="js/messages.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> </head> <body> diff --git a/apps/susidns/src/jsp/details.jsp b/apps/susidns/src/jsp/details.jsp index ceb6f30a7cbbc1ee35827d891e42e4c17efb631e..7e057e387d37b04b530e16ea3fdd9f196e6b74bb 100644 --- a/apps/susidns/src/jsp/details.jsp +++ b/apps/susidns/src/jsp/details.jsp @@ -34,6 +34,7 @@ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>${book.book} <%=intl._t("address book")%> - susidns</title> <link rel="stylesheet" type="text/css" href="<%=book.getTheme()%>susidns.css?<%=net.i2p.CoreVersion.VERSION%>"> +<script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <script src="js/messages.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> </head> <body> diff --git a/apps/susidns/src/jsp/index.jsp b/apps/susidns/src/jsp/index.jsp index f0d4eae45fc7019f7eaba0e78f16c5d51025cf4f..c698518d4cdd2b38ee6e3dc3e63b5bf416467e4f 100644 --- a/apps/susidns/src/jsp/index.jsp +++ b/apps/susidns/src/jsp/index.jsp @@ -46,6 +46,7 @@ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title><%=intl._t("Introduction")%> - SusiDNS</title> <link rel="stylesheet" type="text/css" href="<%=base.getTheme()%>susidns.css?<%=net.i2p.CoreVersion.VERSION%>"> +<script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <script src="js/messages.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> </head> <body> diff --git a/apps/susidns/src/jsp/subscriptions.jsp b/apps/susidns/src/jsp/subscriptions.jsp index 75e3d2ffb729442bfb7e91c82302480fdb8b463c..de8057bfb384fa17cde001c506e916a0d1e6dfe3 100644 --- a/apps/susidns/src/jsp/subscriptions.jsp +++ b/apps/susidns/src/jsp/subscriptions.jsp @@ -36,6 +36,7 @@ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title><%=intl._t("subscriptions")%> - susidns</title> <link rel="stylesheet" type="text/css" href="<%=subs.getTheme()%>susidns.css?<%=net.i2p.CoreVersion.VERSION%>"> +<script src="/js/iframeResizer.contentWindow.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> <script src="js/messages.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> </head> <body> diff --git a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java index b346059ea3934d071d2515ca2a557f67611122ab..ce230391c2e97ab2fff544fa024a17057f3a8331 100644 --- a/apps/susimail/src/src/i2p/susi/webmail/WebMail.java +++ b/apps/susimail/src/src/i2p/susi/webmail/WebMail.java @@ -2401,6 +2401,7 @@ public class WebMail extends HttpServlet out.println( "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=2.0, user-scalable=yes\" />\n" + "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + sessionObject.themePath + "mobile.css?" + CoreVersion.VERSION + "\" />" ); } + out.println("<script src=\"/js/iframeResizer.contentWindow.js?" + CoreVersion.VERSION + "\" type=\"text/javascript\"></script>"); if(state != State.AUTH) out.println("<link rel=\"stylesheet\" href=\"themes/print.css?" + CoreVersion.VERSION + "\" type=\"text/css\" media=\"print\" />"); if (state == State.NEW) { diff --git a/licenses/LICENSE-Iframe-resizer.txt b/licenses/LICENSE-Iframe-resizer.txt new file mode 100644 index 0000000000000000000000000000000000000000..06fd9040cfcda7d5b52c3e200416b78f7c6316dc --- /dev/null +++ b/licenses/LICENSE-Iframe-resizer.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2023 David J. Bradshaw + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.