mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 16:57:16 +01:00
4a6bfc086c
Switched to POCO library for unified platform/library interface. Deprecated the external module API. It was creating more problems than solving. Removed most built-in libraries in favor of system libraries for easier maintenance. Cleaned and secured code with help from static analyzers.
576 lines
15 KiB
JavaScript
576 lines
15 KiB
JavaScript
/*
|
|
* File: iframeSizer.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.
|
|
* Author: David J. Bradshaw - dave@bradshaw.net
|
|
* Contributor: Jure Mav - jure.mav@gmail.com
|
|
*/
|
|
|
|
;(function() {
|
|
'use strict';
|
|
|
|
var
|
|
autoResize = true,
|
|
base = 10,
|
|
bodyBackground = '',
|
|
bodyMargin = 0,
|
|
bodyMarginStr = '',
|
|
bodyPadding = '',
|
|
calculateWidth = false,
|
|
doubleEventList = {'resize':1,'click':1},
|
|
eventCancelTimer = 128,
|
|
height = 1,
|
|
firstRun = true,
|
|
heightCalcModeDefault = 'offset',
|
|
heightCalcMode = heightCalcModeDefault,
|
|
initLock = true,
|
|
initMsg = '',
|
|
interval = 32,
|
|
logging = false,
|
|
msgID = '[iFrameSizer]', //Must match host page msg ID
|
|
msgIdLen = msgID.length,
|
|
myID = '',
|
|
publicMethods = false,
|
|
resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1},
|
|
targetOriginDefault = '*',
|
|
target = window.parent,
|
|
tolerance = 0,
|
|
triggerLocked = false,
|
|
triggerLockedTimer = null,
|
|
width = 1;
|
|
|
|
|
|
function addEventListener(el,evt,func){
|
|
if ('addEventListener' in window){
|
|
el.addEventListener(evt,func, false);
|
|
} else if ('attachEvent' in window){ //IE
|
|
el.attachEvent('on'+evt,func);
|
|
}
|
|
}
|
|
|
|
function formatLogMsg(msg){
|
|
return msgID + '[' + myID + ']' + ' ' + msg;
|
|
}
|
|
|
|
function log(msg){
|
|
if (logging && ('object' === typeof window.console)){
|
|
console.log(formatLogMsg(msg));
|
|
}
|
|
}
|
|
|
|
function warn(msg){
|
|
if ('object' === typeof window.console){
|
|
console.warn(formatLogMsg(msg));
|
|
}
|
|
}
|
|
|
|
|
|
function init(){
|
|
log('Initialising iFrame');
|
|
readData();
|
|
setMargin();
|
|
setBodyStyle('background',bodyBackground);
|
|
setBodyStyle('padding',bodyPadding);
|
|
injectClearFixIntoBodyElement();
|
|
checkHeightMode();
|
|
stopInfiniteResizingOfIFrame();
|
|
setupPublicMethods();
|
|
startEventListeners();
|
|
sendSize('init','Init message from host page');
|
|
}
|
|
|
|
function readData(){
|
|
|
|
var data = initMsg.substr(msgIdLen).split(':');
|
|
|
|
function strBool(str){
|
|
return 'true' === str ? true : false;
|
|
}
|
|
|
|
myID = data[0];
|
|
bodyMargin = (undefined !== data[1]) ? Number(data[1]) : bodyMargin; //For V1 compatibility
|
|
calculateWidth = (undefined !== data[2]) ? strBool(data[2]) : calculateWidth;
|
|
logging = (undefined !== data[3]) ? strBool(data[3]) : logging;
|
|
interval = (undefined !== data[4]) ? Number(data[4]) : interval;
|
|
publicMethods = (undefined !== data[5]) ? strBool(data[5]) : publicMethods;
|
|
autoResize = (undefined !== data[6]) ? strBool(data[6]) : autoResize;
|
|
bodyMarginStr = data[7];
|
|
heightCalcMode = (undefined !== data[8]) ? data[8] : heightCalcMode;
|
|
bodyBackground = data[9];
|
|
bodyPadding = data[10];
|
|
tolerance = (undefined !== data[11]) ? Number(data[11]) : tolerance;
|
|
}
|
|
|
|
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';
|
|
}
|
|
chkCSS('margin',bodyMarginStr);
|
|
setBodyStyle('margin',bodyMarginStr);
|
|
}
|
|
|
|
function stopInfiniteResizingOfIFrame(){
|
|
document.documentElement.style.height = '';
|
|
document.body.style.height = '';
|
|
log('HTML & body height set to "auto"');
|
|
}
|
|
|
|
function initWindowResizeListener(){
|
|
addEventListener(window,'resize', function(){
|
|
sendSize('resize','Window resized');
|
|
});
|
|
}
|
|
|
|
function initWindowClickListener(){
|
|
addEventListener(window,'click', function(){
|
|
sendSize('click','Window clicked');
|
|
});
|
|
}
|
|
|
|
function checkHeightMode(){
|
|
if (heightCalcModeDefault !== heightCalcMode){
|
|
if (!(heightCalcMode in getHeight)){
|
|
warn(heightCalcMode + ' is not a valid option for heightCalculationMethod.');
|
|
heightCalcMode='bodyScroll';
|
|
}
|
|
log('Height calculation method set to "'+heightCalcMode+'"');
|
|
}
|
|
}
|
|
|
|
function startEventListeners(){
|
|
if ( true === autoResize ) {
|
|
initWindowResizeListener();
|
|
initWindowClickListener();
|
|
setupMutationObserver();
|
|
}
|
|
else {
|
|
log('Auto Resize disabled');
|
|
}
|
|
}
|
|
|
|
function injectClearFixIntoBodyElement(){
|
|
var clearFix = document.createElement('div');
|
|
clearFix.style.clear = 'both';
|
|
clearFix.style.display = 'block'; //Guard against this having been globally redefined in CSS.
|
|
document.body.appendChild(clearFix);
|
|
}
|
|
|
|
function setupPublicMethods(){
|
|
if (publicMethods) {
|
|
log('Enable public methods');
|
|
|
|
window.parentIFrame = {
|
|
close: function closeF(){
|
|
sendSize('close','parentIFrame.close()', 0, 0);
|
|
},
|
|
getId: function getIdF(){
|
|
return myID;
|
|
},
|
|
reset: function resetF(){
|
|
resetIFrame('parentIFrame.size');
|
|
},
|
|
scrollTo: function scrollToF(x,y){
|
|
sendMsg(y,x,'scrollTo'); // X&Y reversed at sendMsg uses hieght/width
|
|
},
|
|
sendMessage: function sendMessageF(msg,targetOrigin){
|
|
sendMsg(0,0,'message',msg,targetOrigin);
|
|
},
|
|
setHeightCalculationMethod: function setHeightCalculationMethodF(heightCalculationMethod){
|
|
heightCalcMode = heightCalculationMethod;
|
|
checkHeightMode();
|
|
},
|
|
setTargetOrigin: function setTargetOriginF(targetOrigin){
|
|
log('Set targetOrigin: '+targetOrigin);
|
|
targetOriginDefault = targetOrigin;
|
|
},
|
|
size: function sizeF(customHeight, customWidth){
|
|
var valString = ''+(customHeight?customHeight:'')+(customWidth?','+customWidth:'');
|
|
lockTrigger();
|
|
sendSize('size','parentIFrame.size('+valString+')', customHeight, customWidth);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
function initInterval(){
|
|
if ( 0 !== interval ){
|
|
log('setInterval: '+interval+'ms');
|
|
setInterval(function(){
|
|
sendSize('interval','setInterval: '+interval);
|
|
},Math.abs(interval));
|
|
}
|
|
}
|
|
|
|
function setupInjectElementLoadListners(mutations){
|
|
function addLoadListener(element){
|
|
if (element.height === undefined || element.width === undefined || 0 === element.height || 0 === element.width){
|
|
log('Attach listerner to '+element.src);
|
|
addEventListener(element,'load', function imageLoaded(){
|
|
sendSize('imageLoad','Image loaded');
|
|
});
|
|
}
|
|
}
|
|
|
|
mutations.forEach(function (mutation) {
|
|
if (mutation.type === 'attributes' && mutation.attributeName === 'src'){
|
|
addLoadListener(mutation.target);
|
|
} else if (mutation.type === 'childList'){
|
|
var images = mutation.target.querySelectorAll('img');
|
|
Array.prototype.forEach.call(images,function (image) {
|
|
addLoadListener(image);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function setupMutationObserver(){
|
|
|
|
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
|
|
|
|
function createMutationObserver(){
|
|
var
|
|
target = document.querySelector('body'),
|
|
|
|
config = {
|
|
attributes : true,
|
|
attributeOldValue : false,
|
|
characterData : true,
|
|
characterDataOldValue : false,
|
|
childList : true,
|
|
subtree : true
|
|
},
|
|
|
|
observer = new MutationObserver(function(mutations) {
|
|
sendSize('mutationObserver','mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type);
|
|
setupInjectElementLoadListners(mutations); //Deal with WebKit asyncing image loading when tags are injected into the page
|
|
});
|
|
|
|
log('Enable MutationObserver');
|
|
observer.observe(target, config);
|
|
}
|
|
|
|
if (MutationObserver){
|
|
if (0 > interval) {
|
|
initInterval();
|
|
} else {
|
|
createMutationObserver();
|
|
}
|
|
}
|
|
else {
|
|
warn('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 getBodyOffsetHeight(){
|
|
function getComputedBodyStyle(prop) {
|
|
function convertUnitsToPxForIE8(value) {
|
|
var PIXEL = /^\d+(px)?$/i;
|
|
|
|
if (PIXEL.test(value)) {
|
|
return parseInt(value,base);
|
|
}
|
|
|
|
var
|
|
style = el.style.left,
|
|
runtimeStyle = el.runtimeStyle.left;
|
|
|
|
el.runtimeStyle.left = el.currentStyle.left;
|
|
el.style.left = value || 0;
|
|
value = el.style.pixelLeft;
|
|
el.style.left = style;
|
|
el.runtimeStyle.left = runtimeStyle;
|
|
|
|
return value;
|
|
}
|
|
|
|
var
|
|
el = document.body,
|
|
retVal = 0;
|
|
|
|
if (('defaultView' in document) && ('getComputedStyle' in document.defaultView)) {
|
|
retVal = document.defaultView.getComputedStyle(el, null);
|
|
retVal = (null !== retVal) ? retVal[prop] : 0;
|
|
} else {//IE8
|
|
retVal = convertUnitsToPxForIE8(el.currentStyle[prop]);
|
|
}
|
|
|
|
return parseInt(retVal,base);
|
|
}
|
|
|
|
return document.body.offsetHeight +
|
|
getComputedBodyStyle('marginTop') +
|
|
getComputedBodyStyle('marginBottom');
|
|
}
|
|
|
|
function getBodyScrollHeight(){
|
|
return document.body.scrollHeight;
|
|
}
|
|
|
|
function getDEOffsetHeight(){
|
|
return document.documentElement.offsetHeight;
|
|
}
|
|
|
|
function getDEScrollHeight(){
|
|
return document.documentElement.scrollHeight;
|
|
}
|
|
|
|
//From https://github.com/guardian/iframe-messenger
|
|
function getLowestElementHeight() {
|
|
var
|
|
allElements = document.querySelectorAll('body *'),
|
|
allElementsLength = allElements.length,
|
|
maxBottomVal = 0,
|
|
timer = new Date().getTime();
|
|
|
|
for (var i = 0; i < allElementsLength; i++) {
|
|
if (allElements[i].getBoundingClientRect().bottom > maxBottomVal) {
|
|
maxBottomVal = allElements[i].getBoundingClientRect().bottom;
|
|
}
|
|
}
|
|
|
|
timer = new Date().getTime() - timer;
|
|
|
|
log('Parsed '+allElementsLength+' HTML elements');
|
|
log('LowestElement bottom position calculated in ' + timer + 'ms');
|
|
|
|
return maxBottomVal;
|
|
}
|
|
|
|
function getAllHeights(){
|
|
return [
|
|
getBodyOffsetHeight(),
|
|
getBodyScrollHeight(),
|
|
getDEOffsetHeight(),
|
|
getDEScrollHeight()
|
|
];
|
|
}
|
|
|
|
function getMaxHeight(){
|
|
return Math.max.apply(null,getAllHeights());
|
|
}
|
|
|
|
function getMinHeight(){
|
|
return Math.min.apply(null,getAllHeights());
|
|
}
|
|
|
|
function getBestHeight(){
|
|
return Math.max(getBodyOffsetHeight(),getLowestElementHeight());
|
|
}
|
|
|
|
var getHeight = {
|
|
offset : getBodyOffsetHeight, //Backward compatability
|
|
bodyOffset : getBodyOffsetHeight,
|
|
bodyScroll : getBodyScrollHeight,
|
|
documentElementOffset : getDEOffsetHeight,
|
|
scroll : getDEScrollHeight, //Backward compatability
|
|
documentElementScroll : getDEScrollHeight,
|
|
max : getMaxHeight,
|
|
min : getMinHeight,
|
|
grow : getMaxHeight,
|
|
lowestElement : getBestHeight
|
|
};
|
|
|
|
function getWidth(){
|
|
return Math.max(
|
|
document.documentElement.scrollWidth,
|
|
document.body.scrollWidth
|
|
);
|
|
}
|
|
|
|
function sendSize(triggerEvent, triggerEventDesc, customHeight, customWidth){
|
|
|
|
var currentHeight,currentWidth;
|
|
|
|
function recordTrigger(){
|
|
if (!(triggerEvent in {'reset':1,'resetPage':1,'init':1})){
|
|
log( 'Trigger event: ' + triggerEventDesc );
|
|
}
|
|
}
|
|
|
|
function resizeIFrame(){
|
|
height = currentHeight;
|
|
width = currentWidth;
|
|
|
|
sendMsg(height,width,triggerEvent);
|
|
}
|
|
|
|
function isDoubleFiredEvent(){
|
|
return triggerLocked && (triggerEvent in doubleEventList);
|
|
}
|
|
|
|
function isSizeChangeDetected(){
|
|
function checkTolarance(a,b){
|
|
var retVal = Math.abs(a-b) <= tolerance;
|
|
return !retVal;
|
|
}
|
|
|
|
currentHeight = (undefined !== customHeight) ? customHeight : getHeight[heightCalcMode]();
|
|
currentWidth = (undefined !== customWidth ) ? customWidth : getWidth();
|
|
|
|
return checkTolarance(height,currentHeight) ||
|
|
(calculateWidth && checkTolarance(width,currentWidth));
|
|
|
|
//return (height !== currentHeight) ||
|
|
// (calculateWidth && width !== currentWidth);
|
|
}
|
|
|
|
function isForceResizableEvent(){
|
|
return !(triggerEvent in {'init':1,'interval':1,'size':1});
|
|
}
|
|
|
|
function isForceResizableHeightCalcMode(){
|
|
return (heightCalcMode in resetRequiredMethods);
|
|
}
|
|
|
|
function logIgnored(){
|
|
log('No change in size detected');
|
|
}
|
|
|
|
function checkDownSizing(){
|
|
if (isForceResizableEvent() && isForceResizableHeightCalcMode()){
|
|
resetIFrame(triggerEventDesc);
|
|
} else if (!(triggerEvent in {'interval':1})){
|
|
recordTrigger();
|
|
logIgnored();
|
|
}
|
|
}
|
|
|
|
if (!isDoubleFiredEvent()){
|
|
if (isSizeChangeDetected()){
|
|
recordTrigger();
|
|
lockTrigger();
|
|
resizeIFrame();
|
|
} else {
|
|
checkDownSizing();
|
|
}
|
|
} else {
|
|
log('Trigger event cancelled: '+triggerEvent);
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
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);
|
|
}
|
|
|
|
setTargetOrigin();
|
|
sendToParent();
|
|
}
|
|
|
|
function receiver(event) {
|
|
function isMessageForUs(){
|
|
return msgID === (''+event.data).substr(0,msgIdLen); //''+ Protects against non-string messages
|
|
}
|
|
|
|
function initFromParent(){
|
|
initMsg = event.data;
|
|
target = event.source;
|
|
|
|
init();
|
|
firstRun = false;
|
|
setTimeout(function(){ initLock = false;},eventCancelTimer);
|
|
}
|
|
|
|
function resetFromParent(){
|
|
if (!initLock){
|
|
log('Page size reset by host page');
|
|
triggerReset('resetPage');
|
|
} else {
|
|
log('Page reset ignored by init');
|
|
}
|
|
}
|
|
|
|
function getMessageType(){
|
|
return event.data.split(']')[1];
|
|
}
|
|
|
|
function isMiddleTier(){
|
|
return ('iFrameResize' in window);
|
|
}
|
|
|
|
function isInitMsg(){
|
|
//test if this message is from a child below us. This is an ugly test, however, updating
|
|
//the message format would break backwards compatibity.
|
|
return event.data.split(':')[2] in {'true':1,'false':1};
|
|
}
|
|
|
|
if (isMessageForUs()){
|
|
if (firstRun && isInitMsg()){ //Check msg ID
|
|
initFromParent();
|
|
} else if ('reset' === getMessageType()){
|
|
resetFromParent();
|
|
} else if (event.data !== initMsg && !isMiddleTier()){
|
|
warn('Unexpected message ('+event.data+')');
|
|
}
|
|
}
|
|
}
|
|
|
|
addEventListener(window, 'message', receiver);
|
|
|
|
})();
|