comparison iframe-resizer/src/iframeResizer.contentWindow.js @ 0:ac5f9272033b draft

first upload
author saskia-hiltemann
date Tue, 01 Jul 2014 11:42:23 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:ac5f9272033b
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 })();