Mercurial > repos > saskia-hiltemann > ireport
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 })(); |