iframe-content.js 33 KB

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