You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1052 lines
31 KiB

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