0
|
1 /*
|
|
2 * File: iframeSizer.contentWindow.js
|
|
3 * Desc: Include this file in any page being loaded into an iframe
|
|
4 * to force the iframe to resize to the content size.
|
|
5 * Requires: iframeResizer.js on host page.
|
|
6 * Author: David J. Bradshaw - dave@bradshaw.net
|
|
7 * Contributor: Jure Mav - jure.mav@gmail.com
|
|
8 */
|
|
9
|
|
10 ;(function() {
|
|
11 'use strict';
|
|
12
|
|
13 var
|
|
14 autoResize = true,
|
|
15 base = 10,
|
|
16 bodyBackground = '',
|
|
17 bodyMargin = 0,
|
|
18 bodyMarginStr = '',
|
|
19 bodyPadding = '',
|
|
20 calculateWidth = false,
|
|
21 doubleEventList = {'resize':1,'click':1},
|
|
22 eventCancelTimer = 64,
|
|
23 height = 1,
|
|
24 firstRun = true,
|
|
25 heightCalcModeDefault = 'offset',
|
|
26 heightCalcMode = heightCalcModeDefault,
|
|
27 initLock = true,
|
|
28 initMsg = '',
|
|
29 interval = 32,
|
|
30 logging = false,
|
|
31 msgID = '[iFrameSizer]', //Must match host page msg ID
|
|
32 msgIdLen = msgID.length,
|
|
33 myID = '',
|
|
34 publicMethods = false,
|
|
35 resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1},
|
|
36 targetOriginDefault = '*',
|
|
37 target = window.parent,
|
|
38 tolerance = 0,
|
|
39 triggerLocked = false,
|
|
40 triggerLockedTimer = null,
|
|
41 width = 1;
|
|
42
|
|
43
|
|
44 function addEventListener(el,evt,func){
|
|
45 if ('addEventListener' in window){
|
|
46 el.addEventListener(evt,func, false);
|
|
47 } else if ('attachEvent' in window){ //IE
|
|
48 el.attachEvent('on'+evt,func);
|
|
49 }
|
|
50 }
|
|
51
|
|
52 function formatLogMsg(msg){
|
|
53 return msgID + '[' + myID + ']' + ' ' + msg;
|
|
54 }
|
|
55
|
|
56 function log(msg){
|
|
57 if (logging && ('object' === typeof window.console)){
|
|
58 console.log(formatLogMsg(msg));
|
|
59 }
|
|
60 }
|
|
61
|
|
62 function warn(msg){
|
|
63 if ('object' === typeof window.console){
|
|
64 console.warn(formatLogMsg(msg));
|
|
65 }
|
|
66 }
|
|
67
|
|
68
|
|
69 function init(){
|
|
70 log('Initialising iFrame');
|
|
71 readData();
|
|
72 setMargin();
|
|
73 setBodyStyle('background',bodyBackground);
|
|
74 setBodyStyle('padding',bodyPadding);
|
|
75 injectClearFixIntoBodyElement();
|
|
76 checkHeightMode();
|
|
77 stopInfiniteResizingOfIFrame();
|
|
78 setupPublicMethods();
|
|
79 startEventListeners();
|
|
80 sendSize('init','Init message from host page');
|
|
81 }
|
|
82
|
|
83 function readData(){
|
|
84
|
|
85 var data = initMsg.substr(msgIdLen).split(':');
|
|
86
|
|
87 function strBool(str){
|
|
88 return 'true' === str ? true : false;
|
|
89 }
|
|
90
|
|
91 myID = data[0];
|
|
92 bodyMargin = (undefined !== data[1]) ? Number(data[1]) : bodyMargin; //For V1 compatibility
|
|
93 calculateWidth = (undefined !== data[2]) ? strBool(data[2]) : calculateWidth;
|
|
94 logging = (undefined !== data[3]) ? strBool(data[3]) : logging;
|
|
95 interval = (undefined !== data[4]) ? Number(data[4]) : interval;
|
|
96 publicMethods = (undefined !== data[5]) ? strBool(data[5]) : publicMethods;
|
|
97 autoResize = (undefined !== data[6]) ? strBool(data[6]) : autoResize;
|
|
98 bodyMarginStr = data[7];
|
|
99 heightCalcMode = (undefined !== data[8]) ? data[8] : heightCalcMode;
|
|
100 bodyBackground = data[9];
|
|
101 bodyPadding = data[10];
|
|
102 tolerance = (undefined !== data[11]) ? Number(data[11]) : tolerance;
|
|
103 }
|
|
104
|
|
105 function chkCSS(attr,value){
|
|
106 if (-1 !== value.indexOf('-')){
|
|
107 warn('Negative CSS value ignored for '+attr);
|
|
108 value='';
|
|
109 }
|
|
110 return value;
|
|
111 }
|
|
112
|
|
113 function setBodyStyle(attr,value){
|
|
114 if ((undefined !== value) && ('' !== value) && ('null' !== value)){
|
|
115 document.body.style[attr] = value;
|
|
116 log('Body '+attr+' set to "'+value+'"');
|
|
117 }
|
|
118 }
|
|
119
|
|
120 function setMargin(){
|
|
121 //If called via V1 script, convert bodyMargin from int to str
|
|
122 if (undefined === bodyMarginStr){
|
|
123 bodyMarginStr = bodyMargin+'px';
|
|
124 }
|
|
125 chkCSS('margin',bodyMarginStr);
|
|
126 setBodyStyle('margin',bodyMarginStr);
|
|
127 }
|
|
128
|
|
129 function stopInfiniteResizingOfIFrame(){
|
|
130 document.documentElement.style.height = '';
|
|
131 document.body.style.height = '';
|
|
132 log('HTML & body height set to "auto"');
|
|
133 }
|
|
134
|
|
135 function initWindowResizeListener(){
|
|
136 addEventListener(window,'resize', function(){
|
|
137 sendSize('resize','Window resized');
|
|
138 });
|
|
139 }
|
|
140
|
|
141 function initWindowClickListener(){
|
|
142 addEventListener(window,'click', function(){
|
|
143 sendSize('click','Window clicked');
|
|
144 });
|
|
145 }
|
|
146
|
|
147 function checkHeightMode(){
|
|
148 if (heightCalcModeDefault !== heightCalcMode){
|
|
149 if (!(heightCalcMode in getHeight)){
|
|
150 warn(heightCalcMode + ' is not a valid option for heightCalculationMethod.');
|
|
151 heightCalcMode='bodyScroll';
|
|
152 }
|
|
153 log('Height calculation method set to "'+heightCalcMode+'"');
|
|
154 }
|
|
155 }
|
|
156
|
|
157 function startEventListeners(){
|
|
158 if ( true === autoResize ) {
|
|
159 initWindowResizeListener();
|
|
160 initWindowClickListener();
|
|
161 setupMutationObserver();
|
|
162 }
|
|
163 else {
|
|
164 log('Auto Resize disabled');
|
|
165 }
|
|
166 }
|
|
167
|
|
168 function injectClearFixIntoBodyElement(){
|
|
169 var clearFix = document.createElement('div');
|
|
170 clearFix.style.clear = 'both';
|
|
171 clearFix.style.display = 'block'; //Guard against this having been globally redefined in CSS.
|
|
172 document.body.appendChild(clearFix);
|
|
173 }
|
|
174
|
|
175 function setupPublicMethods(){
|
|
176 if (publicMethods) {
|
|
177 log('Enable public methods');
|
|
178
|
|
179 window.parentIFrame = {
|
|
180 close: function closeF(){
|
|
181 sendSize('close','parentIFrame.close()', 0, 0);
|
|
182 },
|
|
183 getId: function getIdF(){
|
|
184 return myID;
|
|
185 },
|
|
186 reset: function resetF(){
|
|
187 resetIFrame('parentIFrame.size');
|
|
188 },
|
|
189 sendMessage: function sendMessageF(msg,targetOrigin){
|
|
190 sendMsg(0,0,'message',msg,targetOrigin);
|
|
191 },
|
|
192 setHeightCalculationMethod: function setHeightCalculationMethodF(heightCalculationMethod){
|
|
193 heightCalcMode = heightCalculationMethod;
|
|
194 checkHeightMode();
|
|
195 },
|
|
196 setTargetOrigin: function setTargetOriginF(targetOrigin){
|
|
197 log('Set targetOrigin: '+targetOrigin);
|
|
198 targetOriginDefault = targetOrigin;
|
|
199 },
|
|
200 size: function sizeF(customHeight, customWidth){
|
|
201 var valString = ''+(customHeight?customHeight:'')+(customWidth?','+customWidth:'');
|
|
202 lockTrigger();
|
|
203 sendSize('size','parentIFrame.size('+valString+')', customHeight, customWidth);
|
|
204 }
|
|
205 };
|
|
206 }
|
|
207 }
|
|
208
|
|
209 function initInterval(){
|
|
210 if ( 0 !== interval ){
|
|
211 log('setInterval: '+interval+'ms');
|
|
212 setInterval(function(){
|
|
213 sendSize('interval','setInterval: '+interval);
|
|
214 },Math.abs(interval));
|
|
215 }
|
|
216 }
|
|
217
|
|
218 function setupInjectElementLoadListners(mutations){
|
|
219 function addLoadListener(element){
|
|
220 if (element.height === undefined || element.width === undefined || 0 === element.height || 0 === element.width){
|
|
221 log('Attach listerner to '+element.src);
|
|
222 addEventListener(element,'load', function imageLoaded(){
|
|
223 sendSize('imageLoad','Image loaded');
|
|
224 });
|
|
225 }
|
|
226 }
|
|
227
|
|
228 mutations.forEach(function (mutation) {
|
|
229 if (mutation.type === 'attributes' && mutation.attributeName === 'src'){
|
|
230 addLoadListener(mutation.target);
|
|
231 } else if (mutation.type === 'childList'){
|
|
232 var images = mutation.target.querySelectorAll('img');
|
|
233 Array.prototype.forEach.call(images,function (image) {
|
|
234 addLoadListener(image);
|
|
235 });
|
|
236 }
|
|
237 });
|
|
238 }
|
|
239
|
|
240 function setupMutationObserver(){
|
|
241
|
|
242 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
|
|
243
|
|
244 function createMutationObserver(){
|
|
245 var
|
|
246 target = document.querySelector('body'),
|
|
247
|
|
248 config = {
|
|
249 attributes : true,
|
|
250 attributeOldValue : false,
|
|
251 characterData : true,
|
|
252 characterDataOldValue : false,
|
|
253 childList : true,
|
|
254 subtree : true
|
|
255 },
|
|
256
|
|
257 observer = new MutationObserver(function(mutations) {
|
|
258 sendSize('mutationObserver','mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type);
|
|
259 setupInjectElementLoadListners(mutations); //Deal with WebKit asyncing image loading when tags are injected into the page
|
|
260 });
|
|
261
|
|
262 log('Enable MutationObserver');
|
|
263 observer.observe(target, config);
|
|
264 }
|
|
265
|
|
266 if (MutationObserver){
|
|
267 if (0 > interval) {
|
|
268 initInterval();
|
|
269 } else {
|
|
270 createMutationObserver();
|
|
271 }
|
|
272 }
|
|
273 else {
|
|
274 warn('MutationObserver not supported in this browser!');
|
|
275 initInterval();
|
|
276 }
|
|
277 }
|
|
278
|
|
279
|
|
280 // document.documentElement.offsetHeight is not reliable, so
|
|
281 // we have to jump through hoops to get a better value.
|
|
282 function getBodyOffsetHeight(){
|
|
283 function getComputedBodyStyle(prop) {
|
|
284 function convertUnitsToPxForIE8(value) {
|
|
285 var PIXEL = /^\d+(px)?$/i;
|
|
286
|
|
287 if (PIXEL.test(value)) {
|
|
288 return parseInt(value,base);
|
|
289 }
|
|
290
|
|
291 var
|
|
292 style = el.style.left,
|
|
293 runtimeStyle = el.runtimeStyle.left;
|
|
294
|
|
295 el.runtimeStyle.left = el.currentStyle.left;
|
|
296 el.style.left = value || 0;
|
|
297 value = el.style.pixelLeft;
|
|
298 el.style.left = style;
|
|
299 el.runtimeStyle.left = runtimeStyle;
|
|
300
|
|
301 return value;
|
|
302 }
|
|
303
|
|
304 var
|
|
305 el = document.body,
|
|
306 retVal = 0;
|
|
307
|
|
308 if (('defaultView' in document) && ('getComputedStyle' in document.defaultView)) {
|
|
309 retVal = document.defaultView.getComputedStyle(el, null);
|
|
310 retVal = (null !== retVal) ? retVal[prop] : 0;
|
|
311 } else {//IE8
|
|
312 retVal = convertUnitsToPxForIE8(el.currentStyle[prop]);
|
|
313 }
|
|
314
|
|
315 return parseInt(retVal,base);
|
|
316 }
|
|
317
|
|
318 return document.body.offsetHeight +
|
|
319 getComputedBodyStyle('marginTop') +
|
|
320 getComputedBodyStyle('marginBottom');
|
|
321 }
|
|
322
|
|
323 function getBodyScrollHeight(){
|
|
324 return document.body.scrollHeight;
|
|
325 }
|
|
326
|
|
327 function getDEOffsetHeight(){
|
|
328 return document.documentElement.offsetHeight;
|
|
329 }
|
|
330
|
|
331 function getDEScrollHeight(){
|
|
332 return document.documentElement.scrollHeight;
|
|
333 }
|
|
334
|
|
335 //From https://github.com/guardian/iframe-messenger
|
|
336 function getLowestElementHeight() {
|
|
337 var
|
|
338 allElements = document.querySelectorAll('body *'),
|
|
339 allElementsLength = allElements.length,
|
|
340 maxBottomVal = 0,
|
|
341 timer = new Date().getTime();
|
|
342
|
|
343 for (var i = 0; i < allElementsLength; i++) {
|
|
344 if (allElements[i].getBoundingClientRect().bottom > maxBottomVal) {
|
|
345 maxBottomVal = allElements[i].getBoundingClientRect().bottom;
|
|
346 }
|
|
347 }
|
|
348
|
|
349 timer = new Date().getTime() - timer;
|
|
350
|
|
351 log('Parsed '+allElementsLength+' HTML elements');
|
|
352 log('LowestElement bottom position calculated in ' + timer + 'ms');
|
|
353
|
|
354 return maxBottomVal;
|
|
355 }
|
|
356
|
|
357 function getAllHeights(){
|
|
358 return [
|
|
359 getBodyOffsetHeight(),
|
|
360 getBodyScrollHeight(),
|
|
361 getDEOffsetHeight(),
|
|
362 getDEScrollHeight()
|
|
363 ];
|
|
364 }
|
|
365
|
|
366 function getMaxHeight(){
|
|
367 return Math.max.apply(null,getAllHeights());
|
|
368 }
|
|
369
|
|
370 function getMinHeight(){
|
|
371 return Math.min.apply(null,getAllHeights());
|
|
372 }
|
|
373
|
|
374 function getBestHeight(){
|
|
375 return Math.max(getBodyOffsetHeight(),getLowestElementHeight());
|
|
376 }
|
|
377
|
|
378 var getHeight = {
|
|
379 offset : getBodyOffsetHeight, //Backward compatability
|
|
380 bodyOffset : getBodyOffsetHeight,
|
|
381 bodyScroll : getBodyScrollHeight,
|
|
382 documentElementOffset : getDEOffsetHeight,
|
|
383 scroll : getDEScrollHeight, //Backward compatability
|
|
384 documentElementScroll : getDEScrollHeight,
|
|
385 max : getMaxHeight,
|
|
386 min : getMinHeight,
|
|
387 grow : getMaxHeight,
|
|
388 lowestElement : getBestHeight,
|
|
389 };
|
|
390
|
|
391 function getWidth(){
|
|
392 return Math.max(
|
|
393 document.documentElement.scrollWidth,
|
|
394 document.body.scrollWidth
|
|
395 );
|
|
396 }
|
|
397
|
|
398 function sendSize(triggerEvent, triggerEventDesc, customHeight, customWidth){
|
|
399
|
|
400 var currentHeight,currentWidth;
|
|
401
|
|
402 function recordTrigger(){
|
|
403 if (!(triggerEvent in {'reset':1,'resetPage':1,'init':1})){
|
|
404 log( 'Trigger event: ' + triggerEventDesc );
|
|
405 }
|
|
406 }
|
|
407
|
|
408 function resizeIFrame(){
|
|
409 height = currentHeight;
|
|
410 width = currentWidth;
|
|
411
|
|
412 sendMsg(height,width,triggerEvent);
|
|
413 }
|
|
414
|
|
415 function isDoubleFiredEvent(){
|
|
416 return triggerLocked && (triggerEvent in doubleEventList);
|
|
417 }
|
|
418
|
|
419 function isSizeChangeDetected(){
|
|
420 function checkTolarance(a,b){
|
|
421 var retVal = Math.abs(a-b) <= tolerance;
|
|
422 return !retVal;
|
|
423 }
|
|
424
|
|
425 currentHeight = (undefined !== customHeight) ? customHeight : getHeight[heightCalcMode]();
|
|
426 currentWidth = (undefined !== customWidth ) ? customWidth : getWidth();
|
|
427
|
|
428 return checkTolarance(height,currentHeight) ||
|
|
429 (calculateWidth && checkTolarance(width,currentWidth));
|
|
430
|
|
431 //return (height !== currentHeight) ||
|
|
432 // (calculateWidth && width !== currentWidth);
|
|
433 }
|
|
434
|
|
435 function isForceResizableEvent(){
|
|
436 return !(triggerEvent in {'init':1,'interval':1,'size':1});
|
|
437 }
|
|
438
|
|
439 function isForceResizableHeightCalcMode(){
|
|
440 return (heightCalcMode in resetRequiredMethods);
|
|
441 }
|
|
442
|
|
443 function logIgnored(){
|
|
444 log('No change in size detected');
|
|
445 }
|
|
446
|
|
447 function checkDownSizing(){
|
|
448 if (isForceResizableEvent() && isForceResizableHeightCalcMode()){
|
|
449 resetIFrame(triggerEventDesc);
|
|
450 } else if (!(triggerEvent in {'interval':1})){
|
|
451 recordTrigger();
|
|
452 logIgnored();
|
|
453 }
|
|
454 }
|
|
455
|
|
456 if (!isDoubleFiredEvent()){
|
|
457 if (isSizeChangeDetected()){
|
|
458 recordTrigger();
|
|
459 lockTrigger();
|
|
460 resizeIFrame();
|
|
461 } else {
|
|
462 checkDownSizing();
|
|
463 }
|
|
464 } else {
|
|
465 log('Trigger event cancelled: '+triggerEvent);
|
|
466 }
|
|
467 }
|
|
468
|
|
469 function lockTrigger(){
|
|
470 if (!triggerLocked){
|
|
471 triggerLocked = true;
|
|
472 log('Trigger event lock on');
|
|
473 }
|
|
474 clearTimeout(triggerLockedTimer);
|
|
475 triggerLockedTimer = setTimeout(function(){
|
|
476 triggerLocked = false;
|
|
477 log('Trigger event lock off');
|
|
478 log('--');
|
|
479 },eventCancelTimer);
|
|
480 }
|
|
481
|
|
482 function triggerReset(triggerEvent){
|
|
483 height = getHeight[heightCalcMode]();
|
|
484 width = getWidth();
|
|
485
|
|
486 sendMsg(height,width,triggerEvent);
|
|
487 }
|
|
488
|
|
489 function resetIFrame(triggerEventDesc){
|
|
490 var hcm = heightCalcMode;
|
|
491 heightCalcMode = heightCalcModeDefault;
|
|
492
|
|
493 log('Reset trigger event: ' + triggerEventDesc);
|
|
494 lockTrigger();
|
|
495 triggerReset('reset');
|
|
496
|
|
497 heightCalcMode = hcm;
|
|
498 }
|
|
499
|
|
500 function sendMsg(height,width,triggerEvent,msg,targetOrigin){
|
|
501 function setTargetOrigin(){
|
|
502 if (undefined === targetOrigin){
|
|
503 targetOrigin = targetOriginDefault;
|
|
504 } else {
|
|
505 log('Message targetOrigin: '+targetOrigin);
|
|
506 }
|
|
507 }
|
|
508
|
|
509 function sendToParent(){
|
|
510 var
|
|
511 size = height + ':' + width,
|
|
512 message = myID + ':' + size + ':' + triggerEvent + (undefined !== msg ? ':' + msg : '');
|
|
513
|
|
514 log('Sending message to host page (' + message + ')');
|
|
515 target.postMessage( msgID + message, targetOrigin);
|
|
516 }
|
|
517
|
|
518 setTargetOrigin();
|
|
519 sendToParent();
|
|
520 }
|
|
521
|
|
522 function receiver(event) {
|
|
523 function isMessageForUs(){
|
|
524 return msgID === (''+event.data).substr(0,msgIdLen); //''+ Protects against non-string messages
|
|
525 }
|
|
526
|
|
527 function initFromParent(){
|
|
528 initMsg = event.data;
|
|
529 init();
|
|
530 firstRun = false;
|
|
531 setTimeout(function(){ initLock = false;},eventCancelTimer);
|
|
532 }
|
|
533
|
|
534 function resetFromParent(){
|
|
535 if (!initLock){
|
|
536 log('Page size reset by host page');
|
|
537 triggerReset('resetPage');
|
|
538 } else {
|
|
539 log('Page reset ignored by init');
|
|
540 }
|
|
541 }
|
|
542
|
|
543 function getMessageType(){
|
|
544 return event.data.split(']')[1];
|
|
545 }
|
|
546
|
|
547 function isMiddleTier(){
|
|
548 return ('iFrameResize' in window);
|
|
549 }
|
|
550
|
|
551 if (isMessageForUs()){
|
|
552 if (firstRun){ //Check msg ID
|
|
553 initFromParent();
|
|
554 } else if ('reset' === getMessageType()){
|
|
555 resetFromParent();
|
|
556 } else if (event.data !== initMsg && !isMiddleTier()){
|
|
557 warn('Unexpected message ('+event.data+')');
|
|
558 }
|
|
559 }
|
|
560 }
|
|
561
|
|
562 addEventListener(window, 'message', receiver);
|
|
563
|
|
564 })();
|