UNPKG

34.1 kBJavaScriptView Raw
1/*
2 * File: iframeResizer.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 * Doc: https://github.com/davidjbradshaw/iframe-resizer
7 * Author: David J. Bradshaw - dave@bradshaw.net
8 * Contributor: Jure Mav - jure.mav@gmail.com
9 * Contributor: Ian Caunce - ian@hallnet.co.uk
10 */
11
12
13;(function(undefined) {
14 'use strict';
15
16 if(typeof window === 'undefined') return; // don't run for server side render
17
18 var
19 autoResize = true,
20 base = 10,
21 bodyBackground = '',
22 bodyMargin = 0,
23 bodyMarginStr = '',
24 bodyObserver = null,
25 bodyPadding = '',
26 calculateWidth = false,
27 doubleEventList = {'resize':1,'click':1},
28 eventCancelTimer = 128,
29 firstRun = true,
30 height = 1,
31 heightCalcModeDefault = 'bodyOffset',
32 heightCalcMode = heightCalcModeDefault,
33 initLock = true,
34 initMsg = '',
35 inPageLinks = {},
36 interval = 32,
37 intervalTimer = null,
38 logging = false,
39 msgID = '[iFrameSizer]', //Must match host page msg ID
40 msgIdLen = msgID.length,
41 myID = '',
42 observer = null,
43 resetRequiredMethods = {max:1,min:1,bodyScroll:1,documentElementScroll:1},
44 resizeFrom = 'child',
45 sendPermit = true,
46 target = window.parent,
47 targetOriginDefault = '*',
48 tolerance = 0,
49 triggerLocked = false,
50 triggerLockedTimer = null,
51 throttledTimer = 16,
52 width = 1,
53 widthCalcModeDefault = 'scroll',
54 widthCalcMode = widthCalcModeDefault,
55 win = window,
56 messageCallback = function() { warn('MessageCallback function not defined'); },
57 readyCallback = function() {},
58 pageInfoCallback = function() {},
59 customCalcMethods = {
60 height: function() {
61 warn('Custom height calculation function not defined');
62 return document.documentElement.offsetHeight;
63 },
64 width: function() {
65 warn('Custom width calculation function not defined');
66 return document.body.scrollWidth;
67 }
68 },
69 eventHandlersByName = {};
70
71
72 function addEventListener(el,evt,func) {
73 /* istanbul ignore else */ // Not testable in phantonJS
74 if ('addEventListener' in window) {
75 el.addEventListener(evt,func, false);
76 } else if ('attachEvent' in window) { //IE
77 el.attachEvent('on'+evt,func);
78 }
79 }
80
81 function removeEventListener(el,evt,func) {
82 /* istanbul ignore else */ // Not testable in phantonJS
83 if ('removeEventListener' in window) {
84 el.removeEventListener(evt,func, false);
85 } else if ('detachEvent' in window) { //IE
86 el.detachEvent('on'+evt,func);
87 }
88 }
89
90 function capitalizeFirstLetter(string) {
91 return string.charAt(0).toUpperCase() + string.slice(1);
92 }
93
94 //Based on underscore.js
95 function throttle(func) {
96 var
97 context, args, result,
98 timeout = null,
99 previous = 0,
100 later = function() {
101 previous = getNow();
102 timeout = null;
103 result = func.apply(context, args);
104 if (!timeout) {
105 context = args = null;
106 }
107 };
108
109 return function() {
110 var now = getNow();
111
112 if (!previous) {
113 previous = now;
114 }
115
116 var remaining = throttledTimer - (now - previous);
117
118 context = this;
119 args = arguments;
120
121 if (remaining <= 0 || remaining > throttledTimer) {
122 if (timeout) {
123 clearTimeout(timeout);
124 timeout = null;
125 }
126
127 previous = now;
128 result = func.apply(context, args);
129
130 if (!timeout) {
131 context = args = null;
132 }
133
134 } else if (!timeout) {
135 timeout = setTimeout(later, remaining);
136 }
137
138 return result;
139 };
140 }
141
142 var getNow = Date.now || function() {
143 /* istanbul ignore next */ // Not testable in PhantonJS
144 return new Date().getTime();
145 };
146
147 function formatLogMsg(msg) {
148 return msgID + '[' + myID + ']' + ' ' + msg;
149 }
150
151 function log(msg) {
152 if (logging && ('object' === typeof window.console)) {
153 console.log(formatLogMsg(msg));
154 }
155 }
156
157 function warn(msg) {
158 if ('object' === typeof window.console) {
159 console.warn(formatLogMsg(msg));
160 }
161 }
162
163
164 function init() {
165 readDataFromParent();
166 log('Initialising iFrame ('+location.href+')');
167 readDataFromPage();
168 setMargin();
169 setBodyStyle('background',bodyBackground);
170 setBodyStyle('padding',bodyPadding);
171 injectClearFixIntoBodyElement();
172 checkHeightMode();
173 checkWidthMode();
174 stopInfiniteResizingOfIFrame();
175 setupPublicMethods();
176 startEventListeners();
177 inPageLinks = setupInPageLinks();
178 sendSize('init','Init message from host page');
179 readyCallback();
180 }
181
182 function readDataFromParent() {
183
184 function strBool(str) {
185 return 'true' === str ? true : false;
186 }
187
188 var data = initMsg.substr(msgIdLen).split(':');
189
190 myID = data[0];
191 bodyMargin = (undefined !== data[1]) ? Number(data[1]) : bodyMargin; //For V1 compatibility
192 calculateWidth = (undefined !== data[2]) ? strBool(data[2]) : calculateWidth;
193 logging = (undefined !== data[3]) ? strBool(data[3]) : logging;
194 interval = (undefined !== data[4]) ? Number(data[4]) : interval;
195 autoResize = (undefined !== data[6]) ? strBool(data[6]) : autoResize;
196 bodyMarginStr = data[7];
197 heightCalcMode = (undefined !== data[8]) ? data[8] : heightCalcMode;
198 bodyBackground = data[9];
199 bodyPadding = data[10];
200 tolerance = (undefined !== data[11]) ? Number(data[11]) : tolerance;
201 inPageLinks.enable = (undefined !== data[12]) ? strBool(data[12]): false;
202 resizeFrom = (undefined !== data[13]) ? data[13] : resizeFrom;
203 widthCalcMode = (undefined !== data[14]) ? data[14] : widthCalcMode;
204 }
205
206 function readDataFromPage() {
207 function readData() {
208 var data = window.iFrameResizer;
209
210 log('Reading data from page: ' + JSON.stringify(data));
211
212 messageCallback = ('messageCallback' in data) ? data.messageCallback : messageCallback;
213 readyCallback = ('readyCallback' in data) ? data.readyCallback : readyCallback;
214 targetOriginDefault = ('targetOrigin' in data) ? data.targetOrigin : targetOriginDefault;
215 heightCalcMode = ('heightCalculationMethod' in data) ? data.heightCalculationMethod : heightCalcMode;
216 widthCalcMode = ('widthCalculationMethod' in data) ? data.widthCalculationMethod : widthCalcMode;
217 }
218
219 function setupCustomCalcMethods(calcMode, calcFunc) {
220 if ('function' === typeof calcMode) {
221 log('Setup custom ' + calcFunc + 'CalcMethod');
222 customCalcMethods[calcFunc] = calcMode;
223 calcMode = 'custom';
224 }
225
226 return calcMode;
227 }
228
229 if(('iFrameResizer' in window) && (Object === window.iFrameResizer.constructor)) {
230 readData();
231 heightCalcMode = setupCustomCalcMethods(heightCalcMode, 'height');
232 widthCalcMode = setupCustomCalcMethods(widthCalcMode, 'width');
233 }
234
235 log('TargetOrigin for parent set to: ' + targetOriginDefault);
236 }
237
238
239 function chkCSS(attr,value) {
240 if (-1 !== value.indexOf('-')) {
241 warn('Negative CSS value ignored for '+attr);
242 value='';
243 }
244 return value;
245 }
246
247 function setBodyStyle(attr,value) {
248 if ((undefined !== value) && ('' !== value) && ('null' !== value)) {
249 document.body.style[attr] = value;
250 log('Body '+attr+' set to "'+value+'"');
251 }
252 }
253
254 function setMargin() {
255 //If called via V1 script, convert bodyMargin from int to str
256 if (undefined === bodyMarginStr) {
257 bodyMarginStr = bodyMargin+'px';
258 }
259
260 setBodyStyle('margin',chkCSS('margin',bodyMarginStr));
261 }
262
263 function stopInfiniteResizingOfIFrame() {
264 document.documentElement.style.height = '';
265 document.body.style.height = '';
266 log('HTML & body height set to "auto"');
267 }
268
269
270 function manageTriggerEvent(options) {
271
272 var listener = {
273 add: function(eventName) {
274 function handleEvent() {
275 sendSize(options.eventName,options.eventType);
276 }
277
278 eventHandlersByName[eventName] = handleEvent;
279
280 addEventListener(window,eventName,handleEvent);
281 },
282 remove: function(eventName) {
283 var handleEvent = eventHandlersByName[eventName];
284 delete eventHandlersByName[eventName];
285
286 removeEventListener(window,eventName,handleEvent);
287 }
288 };
289
290 if(options.eventNames && Array.prototype.map) {
291 options.eventName = options.eventNames[0];
292 options.eventNames.map(listener[options.method]);
293 } else {
294 listener[options.method](options.eventName);
295 }
296
297 log(capitalizeFirstLetter(options.method) + ' event listener: ' + options.eventType);
298 }
299
300 function manageEventListeners(method) {
301 manageTriggerEvent({method:method, eventType: 'Animation Start', eventNames: ['animationstart','webkitAnimationStart'] });
302 manageTriggerEvent({method:method, eventType: 'Animation Iteration', eventNames: ['animationiteration','webkitAnimationIteration'] });
303 manageTriggerEvent({method:method, eventType: 'Animation End', eventNames: ['animationend','webkitAnimationEnd'] });
304 manageTriggerEvent({method:method, eventType: 'Input', eventName: 'input' });
305 manageTriggerEvent({method:method, eventType: 'Mouse Up', eventName: 'mouseup' });
306 manageTriggerEvent({method:method, eventType: 'Mouse Down', eventName: 'mousedown' });
307 manageTriggerEvent({method:method, eventType: 'Orientation Change', eventName: 'orientationchange' });
308 manageTriggerEvent({method:method, eventType: 'Print', eventName: ['afterprint', 'beforeprint'] });
309 manageTriggerEvent({method:method, eventType: 'Ready State Change', eventName: 'readystatechange' });
310 manageTriggerEvent({method:method, eventType: 'Touch Start', eventName: 'touchstart' });
311 manageTriggerEvent({method:method, eventType: 'Touch End', eventName: 'touchend' });
312 manageTriggerEvent({method:method, eventType: 'Touch Cancel', eventName: 'touchcancel' });
313 manageTriggerEvent({method:method, eventType: 'Transition Start', eventNames: ['transitionstart','webkitTransitionStart','MSTransitionStart','oTransitionStart','otransitionstart'] });
314 manageTriggerEvent({method:method, eventType: 'Transition Iteration', eventNames: ['transitioniteration','webkitTransitionIteration','MSTransitionIteration','oTransitionIteration','otransitioniteration'] });
315 manageTriggerEvent({method:method, eventType: 'Transition End', eventNames: ['transitionend','webkitTransitionEnd','MSTransitionEnd','oTransitionEnd','otransitionend'] });
316 if('child' === resizeFrom) {
317 manageTriggerEvent({method:method, eventType: 'IFrame Resized', eventName: 'resize' });
318 }
319 }
320
321 function checkCalcMode(calcMode,calcModeDefault,modes,type) {
322 if (calcModeDefault !== calcMode) {
323 if (!(calcMode in modes)) {
324 warn(calcMode + ' is not a valid option for '+type+'CalculationMethod.');
325 calcMode=calcModeDefault;
326 }
327 log(type+' calculation method set to "'+calcMode+'"');
328 }
329
330 return calcMode;
331 }
332
333 function checkHeightMode() {
334 heightCalcMode = checkCalcMode(heightCalcMode,heightCalcModeDefault,getHeight,'height');
335 }
336
337 function checkWidthMode() {
338 widthCalcMode = checkCalcMode(widthCalcMode,widthCalcModeDefault,getWidth,'width');
339 }
340
341 function startEventListeners() {
342 if ( true === autoResize ) {
343 manageEventListeners('add');
344 setupMutationObserver();
345 }
346 else {
347 log('Auto Resize disabled');
348 }
349 }
350
351 function stopMsgsToParent() {
352 log('Disable outgoing messages');
353 sendPermit = false;
354 }
355
356 function removeMsgListener() {
357 log('Remove event listener: Message');
358 removeEventListener(window, 'message', receiver);
359 }
360
361 function disconnectMutationObserver() {
362 if (null !== bodyObserver) {
363 /* istanbul ignore next */ // Not testable in PhantonJS
364 bodyObserver.disconnect();
365 }
366 }
367
368 function stopEventListeners() {
369 manageEventListeners('remove');
370 disconnectMutationObserver();
371 clearInterval(intervalTimer);
372 }
373
374 function teardown() {
375 stopMsgsToParent();
376 removeMsgListener();
377 if (true === autoResize) stopEventListeners();
378 }
379
380 function injectClearFixIntoBodyElement() {
381 var clearFix = document.createElement('div');
382 clearFix.style.clear = 'both';
383 clearFix.style.display = 'block'; //Guard against this having been globally redefined in CSS.
384 document.body.appendChild(clearFix);
385 }
386
387 function setupInPageLinks() {
388
389 function getPagePosition () {
390 return {
391 x: (window.pageXOffset !== undefined) ? window.pageXOffset : document.documentElement.scrollLeft,
392 y: (window.pageYOffset !== undefined) ? window.pageYOffset : document.documentElement.scrollTop
393 };
394 }
395
396 function getElementPosition(el) {
397 var
398 elPosition = el.getBoundingClientRect(),
399 pagePosition = getPagePosition();
400
401 return {
402 x: parseInt(elPosition.left,10) + parseInt(pagePosition.x,10),
403 y: parseInt(elPosition.top,10) + parseInt(pagePosition.y,10)
404 };
405 }
406
407 function findTarget(location) {
408 function jumpToTarget(target) {
409 var jumpPosition = getElementPosition(target);
410
411 log('Moving to in page link (#'+hash+') at x: '+jumpPosition.x+' y: '+jumpPosition.y);
412 sendMsg(jumpPosition.y, jumpPosition.x, 'scrollToOffset'); // X&Y reversed at sendMsg uses height/width
413 }
414
415 var
416 hash = location.split('#')[1] || location, //Remove # if present
417 hashData = decodeURIComponent(hash),
418 target = document.getElementById(hashData) || document.getElementsByName(hashData)[0];
419
420 if (undefined !== target) {
421 jumpToTarget(target);
422 } else {
423 log('In page link (#' + hash + ') not found in iFrame, so sending to parent');
424 sendMsg(0,0,'inPageLink','#'+hash);
425 }
426 }
427
428 function checkLocationHash() {
429 if ('' !== location.hash && '#' !== location.hash) {
430 findTarget(location.href);
431 }
432 }
433
434 function bindAnchors() {
435 function setupLink(el) {
436 function linkClicked(e) {
437 e.preventDefault();
438
439 /*jshint validthis:true */
440 findTarget(this.getAttribute('href'));
441 }
442
443 if ('#' !== el.getAttribute('href')) {
444 addEventListener(el,'click',linkClicked);
445 }
446 }
447
448 Array.prototype.forEach.call( document.querySelectorAll( 'a[href^="#"]' ), setupLink );
449 }
450
451 function bindLocationHash() {
452 addEventListener(window,'hashchange',checkLocationHash);
453 }
454
455 function initCheck() { //check if page loaded with location hash after init resize
456 setTimeout(checkLocationHash,eventCancelTimer);
457 }
458
459 function enableInPageLinks() {
460 /* istanbul ignore else */ // Not testable in phantonJS
461 if(Array.prototype.forEach && document.querySelectorAll) {
462 log('Setting up location.hash handlers');
463 bindAnchors();
464 bindLocationHash();
465 initCheck();
466 } else {
467 warn('In page linking not fully supported in this browser! (See README.md for IE8 workaround)');
468 }
469 }
470
471 if(inPageLinks.enable) {
472 enableInPageLinks();
473 } else {
474 log('In page linking not enabled');
475 }
476
477 return {
478 findTarget:findTarget
479 };
480 }
481
482 function setupPublicMethods() {
483 log('Enable public methods');
484
485 win.parentIFrame = {
486
487 autoResize: function autoResizeF(resize) {
488 if (true === resize && false === autoResize) {
489 autoResize=true;
490 startEventListeners();
491 //sendSize('autoResize','Auto Resize enabled');
492 } else if (false === resize && true === autoResize) {
493 autoResize=false;
494 stopEventListeners();
495 }
496
497 return autoResize;
498 },
499
500 close: function closeF() {
501 sendMsg(0,0,'close');
502 teardown();
503 },
504
505 getId: function getIdF() {
506 return myID;
507 },
508
509 getPageInfo: function getPageInfoF(callback) {
510 if ('function' === typeof callback) {
511 pageInfoCallback = callback;
512 sendMsg(0,0,'pageInfo');
513 } else {
514 pageInfoCallback = function() {};
515 sendMsg(0,0,'pageInfoStop');
516 }
517 },
518
519 moveToAnchor: function moveToAnchorF(hash) {
520 inPageLinks.findTarget(hash);
521 },
522
523 reset: function resetF() {
524 resetIFrame('parentIFrame.reset');
525 },
526
527 scrollTo: function scrollToF(x,y) {
528 sendMsg(y,x,'scrollTo'); // X&Y reversed at sendMsg uses height/width
529 },
530
531 scrollToOffset: function scrollToF(x,y) {
532 sendMsg(y,x,'scrollToOffset'); // X&Y reversed at sendMsg uses height/width
533 },
534
535 sendMessage: function sendMessageF(msg,targetOrigin) {
536 sendMsg(0,0,'message',JSON.stringify(msg),targetOrigin);
537 },
538
539 setHeightCalculationMethod: function setHeightCalculationMethodF(heightCalculationMethod) {
540 heightCalcMode = heightCalculationMethod;
541 checkHeightMode();
542 },
543
544 setWidthCalculationMethod: function setWidthCalculationMethodF(widthCalculationMethod) {
545 widthCalcMode = widthCalculationMethod;
546 checkWidthMode();
547 },
548
549 setTargetOrigin: function setTargetOriginF(targetOrigin) {
550 log('Set targetOrigin: '+targetOrigin);
551 targetOriginDefault = targetOrigin;
552 },
553
554 size: function sizeF(customHeight, customWidth) {
555 var valString = ''+(customHeight?customHeight:'')+(customWidth?','+customWidth:'');
556 //lockTrigger();
557 sendSize('size','parentIFrame.size('+valString+')', customHeight, customWidth);
558 }
559 };
560 }
561
562 function initInterval() {
563 if ( 0 !== interval ) {
564 log('setInterval: '+interval+'ms');
565 intervalTimer = setInterval(function() {
566 sendSize('interval','setInterval: '+interval);
567 },Math.abs(interval));
568 }
569 }
570
571 /* istanbul ignore next */ //Not testable in PhantomJS
572 function setupBodyMutationObserver() {
573 function addImageLoadListners(mutation) {
574 function addImageLoadListener(element) {
575 if (false === element.complete) {
576 log('Attach listeners to ' + element.src);
577 element.addEventListener('load', imageLoaded, false);
578 element.addEventListener('error', imageError, false);
579 elements.push(element);
580 }
581 }
582
583 if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
584 addImageLoadListener(mutation.target);
585 } else if (mutation.type === 'childList') {
586 Array.prototype.forEach.call(
587 mutation.target.querySelectorAll('img'),
588 addImageLoadListener
589 );
590 }
591 }
592
593 function removeFromArray(element) {
594 elements.splice(elements.indexOf(element),1);
595 }
596
597 function removeImageLoadListener(element) {
598 log('Remove listeners from ' + element.src);
599 element.removeEventListener('load', imageLoaded, false);
600 element.removeEventListener('error', imageError, false);
601 removeFromArray(element);
602 }
603
604 function imageEventTriggered(event,type,typeDesc) {
605 removeImageLoadListener(event.target);
606 sendSize(type, typeDesc + ': ' + event.target.src, undefined, undefined);
607 }
608
609 function imageLoaded(event) {
610 imageEventTriggered(event,'imageLoad','Image loaded');
611 }
612
613 function imageError(event) {
614 imageEventTriggered(event,'imageLoadFailed','Image load failed');
615 }
616
617 function mutationObserved(mutations) {
618 sendSize('mutationObserver','mutationObserver: ' + mutations[0].target + ' ' + mutations[0].type);
619
620 //Deal with WebKit asyncing image loading when tags are injected into the page
621 mutations.forEach(addImageLoadListners);
622 }
623
624 function createMutationObserver() {
625 var
626 target = document.querySelector('body'),
627
628 config = {
629 attributes : true,
630 attributeOldValue : false,
631 characterData : true,
632 characterDataOldValue : false,
633 childList : true,
634 subtree : true
635 };
636
637 observer = new MutationObserver(mutationObserved);
638
639 log('Create body MutationObserver');
640 observer.observe(target, config);
641
642 return observer;
643 }
644
645 var
646 elements = [],
647 MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
648 observer = createMutationObserver();
649
650 return {
651 disconnect: function () {
652 if ('disconnect' in observer) {
653 log('Disconnect body MutationObserver');
654 observer.disconnect();
655 elements.forEach(removeImageLoadListener);
656 }
657 }
658 };
659 }
660
661 function setupMutationObserver() {
662 var forceIntervalTimer = 0 > interval;
663
664 /* istanbul ignore if */ // Not testable in PhantomJS
665 if (window.MutationObserver || window.WebKitMutationObserver) {
666 if (forceIntervalTimer) {
667 initInterval();
668 } else {
669 bodyObserver = setupBodyMutationObserver();
670 }
671 } else {
672 log('MutationObserver not supported in this browser!');
673 initInterval();
674 }
675 }
676
677
678 // document.documentElement.offsetHeight is not reliable, so
679 // we have to jump through hoops to get a better value.
680 function getComputedStyle(prop,el) {
681 /* istanbul ignore next */ //Not testable in PhantomJS
682 function convertUnitsToPxForIE8(value) {
683 var PIXEL = /^\d+(px)?$/i;
684
685 if (PIXEL.test(value)) {
686 return parseInt(value,base);
687 }
688
689 var
690 style = el.style.left,
691 runtimeStyle = el.runtimeStyle.left;
692
693 el.runtimeStyle.left = el.currentStyle.left;
694 el.style.left = value || 0;
695 value = el.style.pixelLeft;
696 el.style.left = style;
697 el.runtimeStyle.left = runtimeStyle;
698
699 return value;
700 }
701
702 var retVal = 0;
703 el = el || document.body;
704
705 /* istanbul ignore else */ // Not testable in phantonJS
706 if (('defaultView' in document) && ('getComputedStyle' in document.defaultView)) {
707 retVal = document.defaultView.getComputedStyle(el, null);
708 retVal = (null !== retVal) ? retVal[prop] : 0;
709 } else {//IE8
710 retVal = convertUnitsToPxForIE8(el.currentStyle[prop]);
711 }
712
713 return parseInt(retVal,base);
714 }
715
716 function chkEventThottle(timer) {
717 if(timer > throttledTimer/2) {
718 throttledTimer = 2*timer;
719 log('Event throttle increased to ' + throttledTimer + 'ms');
720 }
721 }
722
723 //Idea from https://github.com/guardian/iframe-messenger
724 function getMaxElement(side,elements) {
725 var
726 elementsLength = elements.length,
727 elVal = 0,
728 maxVal = 0,
729 Side = capitalizeFirstLetter(side),
730 timer = getNow();
731
732 for (var i = 0; i < elementsLength; i++) {
733 elVal = elements[i].getBoundingClientRect()[side] + getComputedStyle('margin'+Side,elements[i]);
734 if (elVal > maxVal) {
735 maxVal = elVal;
736 }
737 }
738
739 timer = getNow() - timer;
740
741 log('Parsed '+elementsLength+' HTML elements');
742 log('Element position calculated in ' + timer + 'ms');
743
744 chkEventThottle(timer);
745
746 return maxVal;
747 }
748
749 function getAllMeasurements(dimention) {
750 return [
751 dimention.bodyOffset(),
752 dimention.bodyScroll(),
753 dimention.documentElementOffset(),
754 dimention.documentElementScroll()
755 ];
756 }
757
758 function getTaggedElements(side,tag) {
759 function noTaggedElementsFound() {
760 warn('No tagged elements ('+tag+') found on page');
761 return document.querySelectorAll('body *');
762 }
763
764 var elements = document.querySelectorAll('['+tag+']');
765
766 if (0 === elements.length) noTaggedElementsFound();
767
768 return getMaxElement(side,elements);
769 }
770
771 function getAllElements() {
772 return document.querySelectorAll('body *');
773 }
774
775 var
776 getHeight = {
777 bodyOffset: function getBodyOffsetHeight() {
778 return document.body.offsetHeight + getComputedStyle('marginTop') + getComputedStyle('marginBottom');
779 },
780
781 offset: function() {
782 return getHeight.bodyOffset(); //Backwards compatability
783 },
784
785 bodyScroll: function getBodyScrollHeight() {
786 return document.body.scrollHeight;
787 },
788
789 custom: function getCustomWidth() {
790 return customCalcMethods.height();
791 },
792
793 documentElementOffset: function getDEOffsetHeight() {
794 return document.documentElement.offsetHeight;
795 },
796
797 documentElementScroll: function getDEScrollHeight() {
798 return document.documentElement.scrollHeight;
799 },
800
801 max: function getMaxHeight() {
802 return Math.max.apply(null,getAllMeasurements(getHeight));
803 },
804
805 min: function getMinHeight() {
806 return Math.min.apply(null,getAllMeasurements(getHeight));
807 },
808
809 grow: function growHeight() {
810 return getHeight.max(); //Run max without the forced downsizing
811 },
812
813 lowestElement: function getBestHeight() {
814 return Math.max(getHeight.bodyOffset(), getMaxElement('bottom',getAllElements()));
815 },
816
817 taggedElement: function getTaggedElementsHeight() {
818 return getTaggedElements('bottom','data-iframe-height');
819 }
820 },
821
822 getWidth = {
823 bodyScroll: function getBodyScrollWidth() {
824 return document.body.scrollWidth;
825 },
826
827 bodyOffset: function getBodyOffsetWidth() {
828 return document.body.offsetWidth;
829 },
830
831 custom: function getCustomWidth() {
832 return customCalcMethods.width();
833 },
834
835 documentElementScroll: function getDEScrollWidth() {
836 return document.documentElement.scrollWidth;
837 },
838
839 documentElementOffset: function getDEOffsetWidth() {
840 return document.documentElement.offsetWidth;
841 },
842
843 scroll: function getMaxWidth() {
844 return Math.max(getWidth.bodyScroll(), getWidth.documentElementScroll());
845 },
846
847 max: function getMaxWidth() {
848 return Math.max.apply(null,getAllMeasurements(getWidth));
849 },
850
851 min: function getMinWidth() {
852 return Math.min.apply(null,getAllMeasurements(getWidth));
853 },
854
855 rightMostElement: function rightMostElement() {
856 return getMaxElement('right', getAllElements());
857 },
858
859 taggedElement: function getTaggedElementsWidth() {
860 return getTaggedElements('right', 'data-iframe-width');
861 }
862 };
863
864
865 function sizeIFrame(triggerEvent, triggerEventDesc, customHeight, customWidth) {
866
867 function resizeIFrame() {
868 height = currentHeight;
869 width = currentWidth;
870
871 sendMsg(height,width,triggerEvent);
872 }
873
874 function isSizeChangeDetected() {
875 function checkTolarance(a,b) {
876 var retVal = Math.abs(a-b) <= tolerance;
877 return !retVal;
878 }
879
880 currentHeight = (undefined !== customHeight) ? customHeight : getHeight[heightCalcMode]();
881 currentWidth = (undefined !== customWidth ) ? customWidth : getWidth[widthCalcMode]();
882
883 return checkTolarance(height,currentHeight) || (calculateWidth && checkTolarance(width,currentWidth));
884 }
885
886 function isForceResizableEvent() {
887 return !(triggerEvent in {'init':1,'interval':1,'size':1});
888 }
889
890 function isForceResizableCalcMode() {
891 return (heightCalcMode in resetRequiredMethods) || (calculateWidth && widthCalcMode in resetRequiredMethods);
892 }
893
894 function logIgnored() {
895 log('No change in size detected');
896 }
897
898 function checkDownSizing() {
899 if (isForceResizableEvent() && isForceResizableCalcMode()) {
900 resetIFrame(triggerEventDesc);
901 } else if (!(triggerEvent in {'interval':1})) {
902 logIgnored();
903 }
904 }
905
906 var currentHeight,currentWidth;
907
908 if (isSizeChangeDetected() || 'init' === triggerEvent) {
909 lockTrigger();
910 resizeIFrame();
911 } else {
912 checkDownSizing();
913 }
914 }
915
916 var sizeIFrameThrottled = throttle(sizeIFrame);
917
918 function sendSize(triggerEvent, triggerEventDesc, customHeight, customWidth) {
919 function recordTrigger() {
920 if (!(triggerEvent in {'reset':1,'resetPage':1,'init':1})) {
921 log( 'Trigger event: ' + triggerEventDesc );
922 }
923 }
924
925 function isDoubleFiredEvent() {
926 return triggerLocked && (triggerEvent in doubleEventList);
927 }
928
929 if (!isDoubleFiredEvent()) {
930 recordTrigger();
931 if (triggerEvent === 'init') {
932 sizeIFrame(triggerEvent, triggerEventDesc, customHeight, customWidth);
933 } else {
934 sizeIFrameThrottled(triggerEvent, triggerEventDesc, customHeight, customWidth);
935 }
936 } else {
937 log('Trigger event cancelled: '+triggerEvent);
938 }
939 }
940
941 function lockTrigger() {
942 if (!triggerLocked) {
943 triggerLocked = true;
944 log('Trigger event lock on');
945 }
946 clearTimeout(triggerLockedTimer);
947 triggerLockedTimer = setTimeout(function() {
948 triggerLocked = false;
949 log('Trigger event lock off');
950 log('--');
951 },eventCancelTimer);
952 }
953
954 function triggerReset(triggerEvent) {
955 height = getHeight[heightCalcMode]();
956 width = getWidth[widthCalcMode]();
957
958 sendMsg(height,width,triggerEvent);
959 }
960
961 function resetIFrame(triggerEventDesc) {
962 var hcm = heightCalcMode;
963 heightCalcMode = heightCalcModeDefault;
964
965 log('Reset trigger event: ' + triggerEventDesc);
966 lockTrigger();
967 triggerReset('reset');
968
969 heightCalcMode = hcm;
970 }
971
972 function sendMsg(height,width,triggerEvent,msg,targetOrigin) {
973 function setTargetOrigin() {
974 if (undefined === targetOrigin) {
975 targetOrigin = targetOriginDefault;
976 } else {
977 log('Message targetOrigin: '+targetOrigin);
978 }
979 }
980
981 function sendToParent() {
982 var
983 size = height + ':' + width,
984 message = myID + ':' + size + ':' + triggerEvent + (undefined !== msg ? ':' + msg : '');
985
986 log('Sending message to host page (' + message + ')');
987 target.postMessage( msgID + message, targetOrigin);
988 }
989
990 if(true === sendPermit) {
991 setTargetOrigin();
992 sendToParent();
993 }
994 }
995
996 function receiver(event) {
997 var processRequestFromParent = {
998 init: function initFromParent() {
999 function fireInit() {
1000 initMsg = event.data;
1001 target = event.source;
1002
1003 init();
1004 firstRun = false;
1005 setTimeout(function() { initLock = false;},eventCancelTimer);
1006 }
1007
1008 if (document.readyState === "interactive" || document.readyState === "complete") {
1009 fireInit();
1010 } else {
1011 log('Waiting for page ready');
1012 addEventListener(window,'readystatechange',processRequestFromParent.initFromParent);
1013 }
1014 },
1015
1016 reset: function resetFromParent() {
1017 if (!initLock) {
1018 log('Page size reset by host page');
1019 triggerReset('resetPage');
1020 } else {
1021 log('Page reset ignored by init');
1022 }
1023 },
1024
1025 resize: function resizeFromParent() {
1026 sendSize('resizeParent','Parent window requested size check');
1027 },
1028
1029 moveToAnchor: function moveToAnchorF() {
1030 inPageLinks.findTarget(getData());
1031 },
1032 inPageLink: function inPageLinkF() {this.moveToAnchor();}, //Backward compatability
1033
1034 pageInfo: function pageInfoFromParent() {
1035 var msgBody = getData();
1036 log('PageInfoFromParent called from parent: ' + msgBody );
1037 pageInfoCallback(JSON.parse(msgBody));
1038 log(' --');
1039 },
1040
1041 message: function messageFromParent() {
1042 var msgBody = getData();
1043
1044 log('MessageCallback called from parent: ' + msgBody );
1045 messageCallback(JSON.parse(msgBody));
1046 log(' --');
1047 }
1048 };
1049
1050 function isMessageForUs() {
1051 return msgID === (''+event.data).substr(0,msgIdLen); //''+ Protects against non-string messages
1052 }
1053
1054 function getMessageType() {
1055 return event.data.split(']')[1].split(':')[0];
1056 }
1057
1058 function getData() {
1059 return event.data.substr(event.data.indexOf(':')+1);
1060 }
1061
1062 function isMiddleTier() {
1063 return !(typeof module !== 'undefined' && module.exports) && ('iFrameResize' in window);
1064 }
1065
1066 function isInitMsg() {
1067 //Test if this message is from a child below us. This is an ugly test, however, updating
1068 //the message format would break backwards compatibity.
1069 return event.data.split(':')[2] in {'true':1,'false':1};
1070 }
1071
1072 function callFromParent() {
1073 var messageType = getMessageType();
1074
1075 if (messageType in processRequestFromParent) {
1076 processRequestFromParent[messageType]();
1077 } else if (!isMiddleTier() && !isInitMsg()) {
1078 warn('Unexpected message ('+event.data+')');
1079 }
1080 }
1081
1082 function processMessage() {
1083 if (false === firstRun) {
1084 callFromParent();
1085 } else if (isInitMsg()) {
1086 processRequestFromParent.init();
1087 } else {
1088 log('Ignored message of type "' + getMessageType() + '". Received before initialization.');
1089 }
1090 }
1091
1092 if (isMessageForUs()) {
1093 processMessage();
1094 }
1095 }
1096
1097 //Normally the parent kicks things off when it detects the iFrame has loaded.
1098 //If this script is async-loaded, then tell parent page to retry init.
1099 function chkLateLoaded() {
1100 if('loading' !== document.readyState) {
1101 window.parent.postMessage('[iFrameResizerChild]Ready','*');
1102 }
1103 }
1104
1105 addEventListener(window, 'message', receiver);
1106 chkLateLoaded();
1107
1108
1109
1110})();