UNPKG

31.5 kBJavaScriptView Raw
1/*
2 * File: iframeResizer.js
3 * Desc: Force iframes to size to content.
4 * Requires: iframeResizer.contentWindow.js to be loaded into the target frame.
5 * Doc: https://github.com/davidjbradshaw/iframe-resizer
6 * Author: David J. Bradshaw - dave@bradshaw.net
7 * Contributor: Jure Mav - jure.mav@gmail.com
8 * Contributor: Reed Dadoune - reed@dadoune.com
9 */
10
11
12;(function(undefined) {
13 'use strict';
14
15 if(typeof window === 'undefined') return; // don't run for server side render
16
17 var
18 count = 0,
19 logEnabled = false,
20 hiddenCheckEnabled = false,
21 msgHeader = 'message',
22 msgHeaderLen = msgHeader.length,
23 msgId = '[iFrameSizer]', //Must match iframe msg ID
24 msgIdLen = msgId.length,
25 pagePosition = null,
26 requestAnimationFrame = window.requestAnimationFrame,
27 resetRequiredMethods = {max:1,scroll:1,bodyScroll:1,documentElementScroll:1},
28 settings = {},
29 timer = null,
30 logId = 'Host Page',
31
32 defaults = {
33 autoResize : true,
34 bodyBackground : null,
35 bodyMargin : null,
36 bodyMarginV1 : 8,
37 bodyPadding : null,
38 checkOrigin : true,
39 inPageLinks : false,
40 enablePublicMethods : true,
41 heightCalculationMethod : 'bodyOffset',
42 id : 'iFrameResizer',
43 interval : 32,
44 log : false,
45 maxHeight : Infinity,
46 maxWidth : Infinity,
47 minHeight : 0,
48 minWidth : 0,
49 resizeFrom : 'parent',
50 scrolling : false,
51 sizeHeight : true,
52 sizeWidth : false,
53 warningTimeout : 5000,
54 tolerance : 0,
55 widthCalculationMethod : 'scroll',
56 closedCallback : function() {},
57 initCallback : function() {},
58 messageCallback : function() {warn('MessageCallback function not defined');},
59 resizedCallback : function() {},
60 scrollCallback : function() {return true;}
61 };
62
63 function addEventListener(obj,evt,func) {
64 /* istanbul ignore else */ // Not testable in PhantonJS
65 if ('addEventListener' in window) {
66 obj.addEventListener(evt,func, false);
67 } else if ('attachEvent' in window) {//IE
68 obj.attachEvent('on'+evt,func);
69 }
70 }
71
72 function removeEventListener(el,evt,func) {
73 /* istanbul ignore else */ // Not testable in phantonJS
74 if ('removeEventListener' in window) {
75 el.removeEventListener(evt,func, false);
76 } else if ('detachEvent' in window) { //IE
77 el.detachEvent('on'+evt,func);
78 }
79 }
80
81 function setupRequestAnimationFrame() {
82 var
83 vendors = ['moz', 'webkit', 'o', 'ms'],
84 x;
85
86 // Remove vendor prefixing if prefixed and break early if not
87 for (x = 0; x < vendors.length && !requestAnimationFrame; x += 1) {
88 requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
89 }
90
91 if (!(requestAnimationFrame)) {
92 log('setup','RequestAnimationFrame not supported');
93 }
94 }
95
96 function getMyID(iframeId) {
97 var retStr = 'Host page: '+iframeId;
98
99 if (window.top !== window.self) {
100 if (window.parentIFrame && window.parentIFrame.getId) {
101 retStr = window.parentIFrame.getId()+': '+iframeId;
102 } else {
103 retStr = 'Nested host page: '+iframeId;
104 }
105 }
106
107 return retStr;
108 }
109
110 function formatLogHeader(iframeId) {
111 return msgId + '[' + getMyID(iframeId) + ']';
112 }
113
114 function isLogEnabled(iframeId) {
115 return settings[iframeId] ? settings[iframeId].log : logEnabled;
116 }
117
118 function log(iframeId,msg) {
119 output('log',iframeId,msg,isLogEnabled(iframeId));
120 }
121
122 function info(iframeId,msg) {
123 output('info',iframeId,msg,isLogEnabled(iframeId));
124 }
125
126 function warn(iframeId,msg) {
127 output('warn',iframeId,msg,true);
128 }
129
130 function output(type,iframeId,msg,enabled) {
131 if (true === enabled && 'object' === typeof window.console) {
132 console[type](formatLogHeader(iframeId),msg);
133 }
134 }
135
136 function iFrameListener(event) {
137 function resizeIFrame() {
138 function resize() {
139 setSize(messageData);
140 setPagePosition(iframeId);
141 callback('resizedCallback',messageData);
142 }
143
144 ensureInRange('Height');
145 ensureInRange('Width');
146
147 syncResize(resize,messageData,'init');
148 }
149
150 function processMsg() {
151 var data = msg.substr(msgIdLen).split(':');
152
153 return {
154 iframe: settings[data[0]] && settings[data[0]].iframe,
155 id: data[0],
156 height: data[1],
157 width: data[2],
158 type: data[3]
159 };
160 }
161
162 function ensureInRange(Dimension) {
163 var
164 max = Number(settings[iframeId]['max' + Dimension]),
165 min = Number(settings[iframeId]['min' + Dimension]),
166 dimension = Dimension.toLowerCase(),
167 size = Number(messageData[dimension]);
168
169 log(iframeId,'Checking ' + dimension + ' is in range ' + min + '-' + max);
170
171 if (size<min) {
172 size=min;
173 log(iframeId,'Set ' + dimension + ' to min value');
174 }
175
176 if (size>max) {
177 size=max;
178 log(iframeId,'Set ' + dimension + ' to max value');
179 }
180
181 messageData[dimension] = '' + size;
182 }
183
184
185 function isMessageFromIFrame() {
186 function checkAllowedOrigin() {
187 function checkList() {
188 var
189 i = 0,
190 retCode = false;
191
192 log(iframeId,'Checking connection is from allowed list of origins: ' + checkOrigin);
193
194 for (; i < checkOrigin.length; i++) {
195 if (checkOrigin[i] === origin) {
196 retCode = true;
197 break;
198 }
199 }
200 return retCode;
201 }
202
203 function checkSingle() {
204 var remoteHost = settings[iframeId] && settings[iframeId].remoteHost;
205 log(iframeId,'Checking connection is from: '+remoteHost);
206 return origin === remoteHost;
207 }
208
209 return checkOrigin.constructor === Array ? checkList() : checkSingle();
210 }
211
212 var
213 origin = event.origin,
214 checkOrigin = settings[iframeId] && settings[iframeId].checkOrigin;
215
216 if (checkOrigin && (''+origin !== 'null') && !checkAllowedOrigin()) {
217 throw new Error(
218 'Unexpected message received from: ' + origin +
219 ' for ' + messageData.iframe.id +
220 '. Message was: ' + event.data +
221 '. This error can be disabled by setting the checkOrigin: false option or by providing of array of trusted domains.'
222 );
223 }
224
225 return true;
226 }
227
228 function isMessageForUs() {
229 return msgId === (('' + msg).substr(0,msgIdLen)) && (msg.substr(msgIdLen).split(':')[0] in settings); //''+Protects against non-string msg
230 }
231
232 function isMessageFromMetaParent() {
233 //Test if this message is from a parent above us. This is an ugly test, however, updating
234 //the message format would break backwards compatibity.
235 var retCode = messageData.type in {'true':1,'false':1,'undefined':1};
236
237 if (retCode) {
238 log(iframeId,'Ignoring init message from meta parent page');
239 }
240
241 return retCode;
242 }
243
244 function getMsgBody(offset) {
245 return msg.substr(msg.indexOf(':')+msgHeaderLen+offset);
246 }
247
248 function forwardMsgFromIFrame(msgBody) {
249 log(iframeId,'MessageCallback passed: {iframe: '+ messageData.iframe.id + ', message: ' + msgBody + '}');
250 callback('messageCallback',{
251 iframe: messageData.iframe,
252 message: JSON.parse(msgBody)
253 });
254 log(iframeId,'--');
255 }
256
257 function getPageInfo() {
258 var
259 bodyPosition = document.body.getBoundingClientRect(),
260 iFramePosition = messageData.iframe.getBoundingClientRect();
261
262 return JSON.stringify({
263 iframeHeight: iFramePosition.height,
264 iframeWidth: iFramePosition.width,
265 clientHeight: Math.max(document.documentElement.clientHeight, window.innerHeight || 0),
266 clientWidth: Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
267 offsetTop: parseInt(iFramePosition.top - bodyPosition.top, 10),
268 offsetLeft: parseInt(iFramePosition.left - bodyPosition.left, 10),
269 scrollTop: window.pageYOffset,
270 scrollLeft: window.pageXOffset
271 });
272 }
273
274 function sendPageInfoToIframe(iframe,iframeId) {
275 function debouncedTrigger() {
276 trigger(
277 'Send Page Info',
278 'pageInfo:' + getPageInfo(),
279 iframe,
280 iframeId
281 );
282 }
283
284 debouce(debouncedTrigger,32);
285 }
286
287
288 function startPageInfoMonitor() {
289 function setListener(type,func) {
290 function sendPageInfo() {
291 if (settings[id]) {
292 sendPageInfoToIframe(settings[id].iframe,id);
293 } else {
294 stop();
295 }
296 }
297
298 ['scroll','resize'].forEach(function(evt) {
299 log(id, type + evt + ' listener for sendPageInfo');
300 func(window,evt,sendPageInfo);
301 });
302 }
303
304 function stop() {
305 setListener('Remove ', removeEventListener);
306 }
307
308 function start() {
309 setListener('Add ', addEventListener);
310 }
311
312 var id = iframeId; //Create locally scoped copy of iFrame ID
313
314 start();
315
316 if (settings[id]) {
317 settings[id].stopPageInfo = stop;
318 }
319 }
320
321 function stopPageInfoMonitor() {
322 if (settings[iframeId] && settings[iframeId].stopPageInfo) {
323 settings[iframeId].stopPageInfo();
324 delete settings[iframeId].stopPageInfo;
325 }
326 }
327
328 function checkIFrameExists() {
329 var retBool = true;
330
331 if (null === messageData.iframe) {
332 warn(iframeId,'IFrame ('+messageData.id+') not found');
333 retBool = false;
334 }
335 return retBool;
336 }
337
338 function getElementPosition(target) {
339 var iFramePosition = target.getBoundingClientRect();
340
341 getPagePosition(iframeId);
342
343 return {
344 x: Math.floor( Number(iFramePosition.left) + Number(pagePosition.x) ),
345 y: Math.floor( Number(iFramePosition.top) + Number(pagePosition.y) )
346 };
347 }
348
349 function scrollRequestFromChild(addOffset) {
350 /* istanbul ignore next */ //Not testable in Karma
351 function reposition() {
352 pagePosition = newPosition;
353 scrollTo();
354 log(iframeId,'--');
355 }
356
357 function calcOffset() {
358 return {
359 x: Number(messageData.width) + offset.x,
360 y: Number(messageData.height) + offset.y
361 };
362 }
363
364 function scrollParent() {
365 if (window.parentIFrame) {
366 window.parentIFrame['scrollTo'+(addOffset?'Offset':'')](newPosition.x,newPosition.y);
367 } else {
368 warn(iframeId,'Unable to scroll to requested position, window.parentIFrame not found');
369 }
370 }
371
372 var
373 offset = addOffset ? getElementPosition(messageData.iframe) : {x:0,y:0},
374 newPosition = calcOffset();
375
376 log(iframeId,'Reposition requested from iFrame (offset x:'+offset.x+' y:'+offset.y+')');
377
378 if(window.top !== window.self) {
379 scrollParent();
380 } else {
381 reposition();
382 }
383 }
384
385 function scrollTo() {
386 if (false !== callback('scrollCallback',pagePosition)) {
387 setPagePosition(iframeId);
388 } else {
389 unsetPagePosition();
390 }
391 }
392
393 function findTarget(location) {
394 function jumpToTarget() {
395 var jumpPosition = getElementPosition(target);
396
397 log(iframeId,'Moving to in page link (#'+hash+') at x: '+jumpPosition.x+' y: '+jumpPosition.y);
398 pagePosition = {
399 x: jumpPosition.x,
400 y: jumpPosition.y
401 };
402
403 scrollTo();
404 log(iframeId,'--');
405 }
406
407 function jumpToParent() {
408 if (window.parentIFrame) {
409 window.parentIFrame.moveToAnchor(hash);
410 } else {
411 log(iframeId,'In page link #'+hash+' not found and window.parentIFrame not found');
412 }
413 }
414
415 var
416 hash = location.split('#')[1] || '',
417 hashData = decodeURIComponent(hash),
418 target = document.getElementById(hashData) || document.getElementsByName(hashData)[0];
419
420 if (target) {
421 jumpToTarget();
422 } else if(window.top!==window.self) {
423 jumpToParent();
424 } else {
425 log(iframeId,'In page link #'+hash+' not found');
426 }
427 }
428
429 function callback(funcName,val) {
430 return chkCallback(iframeId,funcName,val);
431 }
432
433 function actionMsg() {
434
435 if(settings[iframeId] && settings[iframeId].firstRun) firstRun();
436
437 switch(messageData.type) {
438 case 'close':
439 if(settings[iframeId].closeRequestCallback) chkCallback(iframeId, 'closeRequestCallback', settings[iframeId].iframe);
440 else closeIFrame(messageData.iframe);
441 break;
442 case 'message':
443 forwardMsgFromIFrame(getMsgBody(6));
444 break;
445 case 'scrollTo':
446 scrollRequestFromChild(false);
447 break;
448 case 'scrollToOffset':
449 scrollRequestFromChild(true);
450 break;
451 case 'pageInfo':
452 sendPageInfoToIframe(settings[iframeId] && settings[iframeId].iframe,iframeId);
453 startPageInfoMonitor();
454 break;
455 case 'pageInfoStop':
456 stopPageInfoMonitor();
457 break;
458 case 'inPageLink':
459 findTarget(getMsgBody(9));
460 break;
461 case 'reset':
462 resetIFrame(messageData);
463 break;
464 case 'init':
465 resizeIFrame();
466 callback('initCallback',messageData.iframe);
467 break;
468 default:
469 resizeIFrame();
470 }
471 }
472
473 function hasSettings(iframeId) {
474 var retBool = true;
475
476 if (!settings[iframeId]) {
477 retBool = false;
478 warn(messageData.type + ' No settings for ' + iframeId + '. Message was: ' + msg);
479 }
480
481 return retBool;
482 }
483
484 function iFrameReadyMsgReceived() {
485 for (var iframeId in settings) {
486 trigger('iFrame requested init',createOutgoingMsg(iframeId),document.getElementById(iframeId),iframeId);
487 }
488 }
489
490 function firstRun() {
491 if (settings[iframeId]) {
492 settings[iframeId].firstRun = false;
493 }
494 }
495
496 function clearWarningTimeout() {
497 if (settings[iframeId]) {
498 clearTimeout(settings[iframeId].msgTimeout);
499 settings[iframeId].warningTimeout = 0;
500 }
501 }
502
503 var
504 msg = event.data,
505 messageData = {},
506 iframeId = null;
507
508 if('[iFrameResizerChild]Ready' === msg) {
509 iFrameReadyMsgReceived();
510 } else if (isMessageForUs()) {
511 messageData = processMsg();
512 iframeId = logId = messageData.id;
513 if (settings[iframeId]) {
514 settings[iframeId].loaded = true;
515 }
516
517 if (!isMessageFromMetaParent() && hasSettings(iframeId)) {
518 log(iframeId,'Received: '+msg);
519
520 if ( checkIFrameExists() && isMessageFromIFrame() ) {
521 actionMsg();
522 }
523 }
524 } else {
525 info(iframeId,'Ignored: '+msg);
526 }
527
528 }
529
530
531 function chkCallback(iframeId,funcName,val) {
532 var
533 func = null,
534 retVal = null;
535
536 if(settings[iframeId]) {
537 func = settings[iframeId][funcName];
538
539 if( 'function' === typeof func) {
540 retVal = func(val);
541 } else {
542 throw new TypeError(funcName+' on iFrame['+iframeId+'] is not a function');
543 }
544 }
545
546 return retVal;
547 }
548
549 function closeIFrame(iframe) {
550 var iframeId = iframe.id;
551
552 log(iframeId,'Removing iFrame: '+iframeId);
553 if (iframe.parentNode) { iframe.parentNode.removeChild(iframe); }
554 chkCallback(iframeId,'closedCallback',iframeId);
555 log(iframeId,'--');
556 delete settings[iframeId];
557 }
558
559 function getPagePosition(iframeId) {
560 if(null === pagePosition) {
561 pagePosition = {
562 x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft,
563 y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop
564 };
565 log(iframeId,'Get page position: '+pagePosition.x+','+pagePosition.y);
566 }
567 }
568
569 function setPagePosition(iframeId) {
570 if(null !== pagePosition) {
571 window.scrollTo(pagePosition.x,pagePosition.y);
572 log(iframeId,'Set page position: '+pagePosition.x+','+pagePosition.y);
573 unsetPagePosition();
574 }
575 }
576
577 function unsetPagePosition() {
578 pagePosition = null;
579 }
580
581 function resetIFrame(messageData) {
582 function reset() {
583 setSize(messageData);
584 trigger('reset','reset',messageData.iframe,messageData.id);
585 }
586
587 log(messageData.id,'Size reset requested by '+('init'===messageData.type?'host page':'iFrame'));
588 getPagePosition(messageData.id);
589 syncResize(reset,messageData,'reset');
590 }
591
592 function setSize(messageData) {
593 function setDimension(dimension) {
594 messageData.iframe.style[dimension] = messageData[dimension] + 'px';
595 log(
596 messageData.id,
597 'IFrame (' + iframeId +
598 ') ' + dimension +
599 ' set to ' + messageData[dimension] + 'px'
600 );
601 }
602
603 function chkZero(dimension) {
604 //FireFox sets dimension of hidden iFrames to zero.
605 //So if we detect that set up an event to check for
606 //when iFrame becomes visible.
607
608 /* istanbul ignore next */ //Not testable in PhantomJS
609 if (!hiddenCheckEnabled && '0' === messageData[dimension]) {
610 hiddenCheckEnabled = true;
611 log(iframeId,'Hidden iFrame detected, creating visibility listener');
612 fixHiddenIFrames();
613 }
614 }
615
616 function processDimension(dimension) {
617 setDimension(dimension);
618 chkZero(dimension);
619 }
620
621 var iframeId = messageData.iframe.id;
622
623 if(settings[iframeId]) {
624 if( settings[iframeId].sizeHeight) { processDimension('height'); }
625 if( settings[iframeId].sizeWidth ) { processDimension('width'); }
626 }
627 }
628
629 function syncResize(func,messageData,doNotSync) {
630 /* istanbul ignore if */ //Not testable in PhantomJS
631 if(doNotSync!==messageData.type && requestAnimationFrame) {
632 log(messageData.id,'Requesting animation frame');
633 requestAnimationFrame(func);
634 } else {
635 func();
636 }
637 }
638
639 function trigger(calleeMsg, msg, iframe, id, noResponseWarning) {
640 function postMessageToIFrame() {
641 var target = settings[id] && settings[id].targetOrigin;
642 log(id,'[' + calleeMsg + '] Sending msg to iframe['+id+'] ('+msg+') targetOrigin: '+target);
643 iframe.contentWindow.postMessage( msgId + msg, target );
644 }
645
646 function iFrameNotFound() {
647 warn(id,'[' + calleeMsg + '] IFrame('+id+') not found');
648 }
649
650 function chkAndSend() {
651 if(iframe && 'contentWindow' in iframe && (null !== iframe.contentWindow)) { //Null test for PhantomJS
652 postMessageToIFrame();
653 } else {
654 iFrameNotFound();
655 }
656 }
657
658 function warnOnNoResponse() {
659 function warning() {
660 if (settings[id] && !settings[id].loaded && !errorShown) {
661 errorShown = true;
662 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 ingored if everything is working, or you can set the warningTimeout option to a higher value or zero to suppress this warning.');
663 }
664 }
665
666 if (!!noResponseWarning && settings[id] && !!settings[id].warningTimeout) {
667 settings[id].msgTimeout = setTimeout(warning, settings[id].warningTimeout);
668 }
669 }
670
671 var errorShown = false;
672
673 id = id || iframe.id;
674
675 if(settings[id]) {
676 chkAndSend();
677 warnOnNoResponse();
678 }
679
680 }
681
682 function createOutgoingMsg(iframeId) {
683 return iframeId +
684 ':' + settings[iframeId].bodyMarginV1 +
685 ':' + settings[iframeId].sizeWidth +
686 ':' + settings[iframeId].log +
687 ':' + settings[iframeId].interval +
688 ':' + settings[iframeId].enablePublicMethods +
689 ':' + settings[iframeId].autoResize +
690 ':' + settings[iframeId].bodyMargin +
691 ':' + settings[iframeId].heightCalculationMethod +
692 ':' + settings[iframeId].bodyBackground +
693 ':' + settings[iframeId].bodyPadding +
694 ':' + settings[iframeId].tolerance +
695 ':' + settings[iframeId].inPageLinks +
696 ':' + settings[iframeId].resizeFrom +
697 ':' + settings[iframeId].widthCalculationMethod;
698 }
699
700 function setupIFrame(iframe,options) {
701 function setLimits() {
702 function addStyle(style) {
703 if ((Infinity !== settings[iframeId][style]) && (0 !== settings[iframeId][style])) {
704 iframe.style[style] = settings[iframeId][style] + 'px';
705 log(iframeId,'Set '+style+' = '+settings[iframeId][style]+'px');
706 }
707 }
708
709 function chkMinMax(dimension) {
710 if (settings[iframeId]['min'+dimension]>settings[iframeId]['max'+dimension]) {
711 throw new Error('Value for min'+dimension+' can not be greater than max'+dimension);
712 }
713 }
714
715 chkMinMax('Height');
716 chkMinMax('Width');
717
718 addStyle('maxHeight');
719 addStyle('minHeight');
720 addStyle('maxWidth');
721 addStyle('minWidth');
722 }
723
724 function newId() {
725 var id = ((options && options.id) || defaults.id + count++);
726 if (null !== document.getElementById(id)) {
727 id = id + count++;
728 }
729 return id;
730 }
731
732 function ensureHasId(iframeId) {
733 logId=iframeId;
734 if (''===iframeId) {
735 iframe.id = iframeId = newId();
736 logEnabled = (options || {}).log;
737 logId=iframeId;
738 log(iframeId,'Added missing iframe ID: '+ iframeId +' (' + iframe.src + ')');
739 }
740
741
742 return iframeId;
743 }
744
745 function setScrolling() {
746 log(iframeId,'IFrame scrolling ' + (settings[iframeId] && settings[iframeId].scrolling ? 'enabled' : 'disabled') + ' for ' + iframeId);
747 iframe.style.overflow = false === (settings[iframeId] && settings[iframeId].scrolling) ? 'hidden' : 'auto';
748 switch(settings[iframeId] && settings[iframeId].scrolling) {
749 case true:
750 iframe.scrolling = 'yes';
751 break;
752 case false:
753 iframe.scrolling = 'no';
754 break;
755 default:
756 iframe.scrolling = settings[iframeId] ? settings[iframeId].scrolling : 'no';
757 }
758 }
759
760 //The V1 iFrame script expects an int, where as in V2 expects a CSS
761 //string value such as '1px 3em', so if we have an int for V2, set V1=V2
762 //and then convert V2 to a string PX value.
763 function setupBodyMarginValues() {
764 if (('number'===typeof(settings[iframeId] && settings[iframeId].bodyMargin)) || ('0'===(settings[iframeId] && settings[iframeId].bodyMargin))) {
765 settings[iframeId].bodyMarginV1 = settings[iframeId].bodyMargin;
766 settings[iframeId].bodyMargin = '' + settings[iframeId].bodyMargin + 'px';
767 }
768 }
769
770 function checkReset() {
771 // Reduce scope of firstRun to function, because IE8's JS execution
772 // context stack is borked and this value gets externally
773 // changed midway through running this function!!!
774 var
775 firstRun = settings[iframeId] && settings[iframeId].firstRun,
776 resetRequertMethod = settings[iframeId] && settings[iframeId].heightCalculationMethod in resetRequiredMethods;
777
778 if (!firstRun && resetRequertMethod) {
779 resetIFrame({iframe:iframe, height:0, width:0, type:'init'});
780 }
781 }
782
783 function setupIFrameObject() {
784 if(Function.prototype.bind && settings[iframeId]) { //Ignore unpolyfilled IE8.
785 settings[iframeId].iframe.iFrameResizer = {
786
787 close : closeIFrame.bind(null,settings[iframeId].iframe),
788
789 resize : trigger.bind(null,'Window resize', 'resize', settings[iframeId].iframe),
790
791 moveToAnchor : function(anchor) {
792 trigger('Move to anchor','moveToAnchor:'+anchor, settings[iframeId].iframe,iframeId);
793 },
794
795 sendMessage : function(message) {
796 message = JSON.stringify(message);
797 trigger('Send Message','message:'+message, settings[iframeId].iframe, iframeId);
798 }
799 };
800 }
801 }
802
803 //We have to call trigger twice, as we can not be sure if all
804 //iframes have completed loading when this code runs. The
805 //event listener also catches the page changing in the iFrame.
806 function init(msg) {
807 function iFrameLoaded() {
808 trigger('iFrame.onload', msg, iframe, undefined , true);
809 checkReset();
810 }
811
812 addEventListener(iframe,'load',iFrameLoaded);
813 trigger('init', msg, iframe, undefined, true);
814 }
815
816 function checkOptions(options) {
817 if ('object' !== typeof options) {
818 throw new TypeError('Options is not an object');
819 }
820 }
821
822 function copyOptions(options) {
823 for (var option in defaults) {
824 if (defaults.hasOwnProperty(option)) {
825 settings[iframeId][option] = options.hasOwnProperty(option) ? options[option] : defaults[option];
826 }
827 }
828 }
829
830 function getTargetOrigin (remoteHost) {
831 return ('' === remoteHost || 'file://' === remoteHost) ? '*' : remoteHost;
832 }
833
834 function processOptions(options) {
835 options = options || {};
836 settings[iframeId] = {
837 firstRun : true,
838 iframe : iframe,
839 remoteHost : iframe.src.split('/').slice(0,3).join('/')
840 };
841
842 checkOptions(options);
843 copyOptions(options);
844
845 if (settings[iframeId]) {
846 settings[iframeId].targetOrigin = true === settings[iframeId].checkOrigin ? getTargetOrigin(settings[iframeId].remoteHost) : '*';
847 }
848 }
849
850 function beenHere() {
851 return (iframeId in settings && 'iFrameResizer' in iframe);
852 }
853
854 var iframeId = ensureHasId(iframe.id);
855
856 if (!beenHere()) {
857 processOptions(options);
858 setScrolling();
859 setLimits();
860 setupBodyMarginValues();
861 init(createOutgoingMsg(iframeId));
862 setupIFrameObject();
863 } else {
864 warn(iframeId,'Ignored iFrame, already setup.');
865 }
866 }
867
868 function debouce(fn,time) {
869 if (null === timer) {
870 timer = setTimeout(function() {
871 timer = null;
872 fn();
873 }, time);
874 }
875 }
876
877 /* istanbul ignore next */ //Not testable in PhantomJS
878 function fixHiddenIFrames() {
879 function checkIFrames() {
880 function checkIFrame(settingId) {
881 function chkDimension(dimension) {
882 return '0px' === (settings[settingId] && settings[settingId].iframe.style[dimension]);
883 }
884
885 function isVisible(el) {
886 return (null !== el.offsetParent);
887 }
888
889 if (settings[settingId] && isVisible(settings[settingId].iframe) && (chkDimension('height') || chkDimension('width'))) {
890 trigger('Visibility change', 'resize', settings[settingId].iframe, settingId);
891 }
892 }
893
894 for (var settingId in settings) {
895 checkIFrame(settingId);
896 }
897 }
898
899 function mutationObserved(mutations) {
900 log('window','Mutation observed: ' + mutations[0].target + ' ' + mutations[0].type);
901 debouce(checkIFrames,16);
902 }
903
904 function createMutationObserver() {
905 var
906 target = document.querySelector('body'),
907
908 config = {
909 attributes : true,
910 attributeOldValue : false,
911 characterData : true,
912 characterDataOldValue : false,
913 childList : true,
914 subtree : true
915 },
916
917 observer = new MutationObserver(mutationObserved);
918
919 observer.observe(target, config);
920 }
921
922 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
923
924 if (MutationObserver) createMutationObserver();
925 }
926
927
928 function resizeIFrames(event) {
929 function resize() {
930 sendTriggerMsg('Window '+event,'resize');
931 }
932
933 log('window','Trigger event: '+event);
934 debouce(resize,16);
935 }
936
937 /* istanbul ignore next */ //Not testable in PhantomJS
938 function tabVisible() {
939 function resize() {
940 sendTriggerMsg('Tab Visable','resize');
941 }
942
943 if('hidden' !== document.visibilityState) {
944 log('document','Trigger event: Visiblity change');
945 debouce(resize,16);
946 }
947 }
948
949 function sendTriggerMsg(eventName,event) {
950 function isIFrameResizeEnabled(iframeId) {
951 return settings[iframeId] &&
952 'parent' === settings[iframeId].resizeFrom &&
953 settings[iframeId].autoResize &&
954 !settings[iframeId].firstRun;
955 }
956
957 for (var iframeId in settings) {
958 if(isIFrameResizeEnabled(iframeId)) {
959 trigger(eventName, event, document.getElementById(iframeId), iframeId);
960 }
961 }
962 }
963
964 function setupEventListeners() {
965 addEventListener(window,'message',iFrameListener);
966
967 addEventListener(window,'resize', function() {resizeIFrames('resize');});
968
969 addEventListener(document,'visibilitychange',tabVisible);
970 addEventListener(document,'-webkit-visibilitychange',tabVisible); //Andriod 4.4
971 addEventListener(window,'focusin',function() {resizeIFrames('focus');}); //IE8-9
972 addEventListener(window,'focus',function() {resizeIFrames('focus');});
973 }
974
975
976 function factory() {
977 function init(options,element) {
978 function chkType() {
979 if(!element.tagName) {
980 throw new TypeError('Object is not a valid DOM element');
981 } else if ('IFRAME' !== element.tagName.toUpperCase()) {
982 throw new TypeError('Expected <IFRAME> tag, found <'+element.tagName+'>');
983 }
984 }
985
986 if(element) {
987 chkType();
988 setupIFrame(element, options);
989 iFrames.push(element);
990 }
991 }
992
993 function warnDeprecatedOptions(options) {
994 if (options && options.enablePublicMethods) {
995 warn('enablePublicMethods option has been removed, public methods are now always available in the iFrame');
996 }
997 }
998
999 var iFrames;
1000
1001 setupRequestAnimationFrame();
1002 setupEventListeners();
1003
1004 return function iFrameResizeF(options,target) {
1005 iFrames = []; //Only return iFrames past in on this call
1006
1007 warnDeprecatedOptions(options);
1008
1009 switch (typeof(target)) {
1010 case 'undefined':
1011 case 'string':
1012 Array.prototype.forEach.call(
1013 document.querySelectorAll( target || 'iframe' ),
1014 init.bind(undefined, options)
1015 );
1016 break;
1017 case 'object':
1018 init(options,target);
1019 break;
1020 default:
1021 throw new TypeError('Unexpected data type ('+typeof(target)+')');
1022 }
1023
1024 return iFrames;
1025 };
1026 }
1027
1028 function createJQueryPublicMethod($) {
1029 if (!$.fn) {
1030 info('','Unable to bind to jQuery, it is not fully loaded.');
1031 } else if (!$.fn.iFrameResize) {
1032 $.fn.iFrameResize = function $iFrameResizeF(options) {
1033 function init(index, element) {
1034 setupIFrame(element, options);
1035 }
1036
1037 return this.filter('iframe').each(init).end();
1038 };
1039 }
1040 }
1041
1042 if (window.jQuery) { createJQueryPublicMethod(window.jQuery); }
1043
1044 if (typeof define === 'function' && define.amd) {
1045 define([],factory);
1046 } else if (typeof module === 'object' && typeof module.exports === 'object') { //Node for browserfy
1047 module.exports = factory();
1048 } else {
1049 window.iFrameResize = window.iFrameResize || factory();
1050 }
1051
1052})();