From 962bc129cd055071c526f2f4992619a5cec9978e Mon Sep 17 00:00:00 2001 From: suziwen Date: Fri, 24 May 2024 19:03:49 +0800 Subject: [PATCH] Compatible with Arc browser --- .../overlay/manifest-firefox.json | 2 +- .../overlay/manifest.json | 4 +- .../overlay/popup-iframe.html | 22 + .../overlay/popup-iframe.js | 14 + .../src/module/external_api.coffee | 6 +- .../src/module/options.coffee | 3 +- .../iframeResizer.contentWindow.min.js | 1305 +++++++++++++++ .../lib/iframeResizer/iframeResizer.min.js | 1466 +++++++++++++++++ omega-web/lib/themes/base.css | 14 +- omega-web/src/coffee/popup.coffee | 14 +- omega-web/src/less/popup.less | 3 +- omega-web/src/popup.jade | 1 + omega-web/src/popup/css/index.css | 4 +- omega-web/src/popup/index.html | 1 + omega-web/src/popup/js/index.js | 2 +- .../src/popup/js/proxy_not_controllable.js | 2 +- 16 files changed, 2844 insertions(+), 19 deletions(-) create mode 100644 omega-target-chromium-extension/overlay/popup-iframe.html create mode 100644 omega-target-chromium-extension/overlay/popup-iframe.js create mode 100644 omega-web/lib/iframeResizer/iframeResizer.contentWindow.min.js create mode 100644 omega-web/lib/iframeResizer/iframeResizer.min.js diff --git a/omega-target-chromium-extension/overlay/manifest-firefox.json b/omega-target-chromium-extension/overlay/manifest-firefox.json index a5d535a..bbd538d 100644 --- a/omega-target-chromium-extension/overlay/manifest-firefox.json +++ b/omega-target-chromium-extension/overlay/manifest-firefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "ZeroOmega--Proxy Switchy manifest v3 version", - "version": "3.2.1", + "version": "3.2.2", "description": "__MSG_manifest_app_description__", "icons": { "16": "img/icons/omega-action-16.png", diff --git a/omega-target-chromium-extension/overlay/manifest.json b/omega-target-chromium-extension/overlay/manifest.json index d3aaca6..8b5ac7c 100644 --- a/omega-target-chromium-extension/overlay/manifest.json +++ b/omega-target-chromium-extension/overlay/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "ZeroOmega--Proxy Switchy manifest v3 version", - "version": "3.2.1", + "version": "3.2.2", "description": "__MSG_manifest_app_description__", "icons": { "16": "img/icons/omega-action-16.png", @@ -20,7 +20,7 @@ "32": "img/icons/omega-action-32.png" }, "default_title": "__MSG_manifest_icon_default_title__", - "default_popup": "popup/index.html" + "default_popup": "popup-iframe.html" }, "background": { "service_worker": "x-background.js", diff --git a/omega-target-chromium-extension/overlay/popup-iframe.html b/omega-target-chromium-extension/overlay/popup-iframe.html new file mode 100644 index 0000000..31f1e06 --- /dev/null +++ b/omega-target-chromium-extension/overlay/popup-iframe.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/omega-target-chromium-extension/overlay/popup-iframe.js b/omega-target-chromium-extension/overlay/popup-iframe.js new file mode 100644 index 0000000..03d91f3 --- /dev/null +++ b/omega-target-chromium-extension/overlay/popup-iframe.js @@ -0,0 +1,14 @@ + +document.location.href = './popup/index.html' +// arc browser can't use location.href to change. +// https://github.com/suziwen/ZeroOmega/issues/4 +$script('js/omega_target_popup.js', 'om-target', function() { + $script('popup/js/style.js', 'om-style') + iFrameResize({ + sizeWidth: true, + autoResize: true, + resizeFrom: 'child', + heightCalculationMethod: 'bodyScroll', + widthCalculationMethod: 'bodyOffset' + }, '#myIframe') +}) diff --git a/omega-target-chromium-extension/src/module/external_api.coffee b/omega-target-chromium-extension/src/module/external_api.coffee index 6e172c1..fa006fd 100644 --- a/omega-target-chromium-extension/src/module/external_api.coffee +++ b/omega-target-chromium-extension/src/module/external_api.coffee @@ -22,7 +22,8 @@ module.exports = class ExternalApi return unless @disabled @options.setProxyNotControllable(null) - chrome.action.setPopup?({popup: 'popup/index.html'}) + #chrome.action.setPopup?({popup: 'popup/index.html'}) + chrome.action.setPopup?({popup: 'popup-iframe.html'}) @options.reloadQuickSwitch() @disabled = false @options.clearBadge() @@ -49,7 +50,8 @@ module.exports = class ExternalApi if @knownExts[port.sender.id] >= 32 reason = 'upgrade' @options.setProxyNotControllable reason, {text: 'X', color: '#5ab432'} - chrome.action.setPopup?({popup: 'popup/index.html'}) + #chrome.action.setPopup?({popup: 'popup/index.html'}) + chrome.action.setPopup?({popup: 'popup-iframe.html'}) port.postMessage({action: 'state', state: 'disabled'}) when 'enable' @reenable() diff --git a/omega-target-chromium-extension/src/module/options.coffee b/omega-target-chromium-extension/src/module/options.coffee index 31312f6..c72774c 100644 --- a/omega-target-chromium-extension/src/module/options.coffee +++ b/omega-target-chromium-extension/src/module/options.coffee @@ -169,7 +169,8 @@ class ChromeOptions extends OmegaTarget.Options if quickSwitch chrome.action.setPopup({popup: ''}) else - chrome.action.setPopup({popup: 'popup/index.html'}) + #chrome.action.setPopup({popup: 'popup/index.html'}) + chrome.action.setPopup({popup: 'popup-iframe.html'}) chrome.contextMenus?.update('enableQuickSwitch', {checked: !!quickSwitch}) Promise.resolve() diff --git a/omega-web/lib/iframeResizer/iframeResizer.contentWindow.min.js b/omega-web/lib/iframeResizer/iframeResizer.contentWindow.min.js new file mode 100644 index 0000000..d32844e --- /dev/null +++ b/omega-web/lib/iframeResizer/iframeResizer.contentWindow.min.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/omega-web/lib/iframeResizer/iframeResizer.min.js b/omega-web/lib/iframeResizer/iframeResizer.min.js new file mode 100644 index 0000000..4ea1d30 --- /dev/null +++ b/omega-web/lib/iframeResizer/iframeResizer.min.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