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 - [email protected] 7 * Contributor: Jure Mav - [email protected] 8 * Contributor: Reed Dadoune - [email protected] 9 */ 10 11// eslint-disable-next-line sonarjs/cognitive-complexity, no-shadow-restricted-names 12;(function (undefined) { 13 if (typeof window === 'undefined') return // don't run for server side render 14 15 var count = 0, 16 logEnabled = false, 17 hiddenCheckEnabled = false, 18 msgHeader = 'message', 19 msgHeaderLen = msgHeader.length, 20 msgId = '[iFrameSizer]', // Must match iframe msg ID 21 msgIdLen = msgId.length, 22 pagePosition = null, 23 requestAnimationFrame = window.requestAnimationFrame, 24 resetRequiredMethods = { 25 max: 1, 26 scroll: 1, 27 bodyScroll: 1, 28 documentElementScroll: 1 29 }, 30 settings = {}, 31 timer = null, 32 defaults = { 33 autoResize: true, 34 bodyBackground: null, 35 bodyMargin: null, 36 bodyMarginV1: 8, 37 bodyPadding: null, 38 checkOrigin: true, 39 inPageLinks: false, 40 enablePublicMethods: true, 41 heightCalculationMethod: 'bodyOffset', 42 id: 'iFrameResizer', 43 interval: 32, 44 log: false, 45 maxHeight: Infinity, 46 maxWidth: Infinity, 47 minHeight: 0, 48 minWidth: 0, 49 mouseEvents: true, 50 resizeFrom: 'parent', 51 scrolling: false, 52 sizeHeight: true, 53 sizeWidth: false, 54 warningTimeout: 5000, 55 tolerance: 0, 56 widthCalculationMethod: 'scroll', 57 onClose: function () { 58 return true 59 }, 60 onClosed: function () {}, 61 onInit: function () {}, 62 onMessage: function () { 63 warn('onMessage function not defined') 64 }, 65 onMouseEnter: function () {}, 66 onMouseLeave: function () {}, 67 onResized: function () {}, 68 onScroll: function () { 69 return true 70 } 71 } 72 73 function getMutationObserver() { 74 return ( 75 window.MutationObserver || 76 window.WebKitMutationObserver || 77 window.MozMutationObserver 78 ) 79 } 80 81 function addEventListener(el, evt, func) { 82 el.addEventListener(evt, func, false) 83 } 84 85 function removeEventListener(el, evt, func) { 86 el.removeEventListener(evt, func, false) 87 } 88 89 function setupRequestAnimationFrame() { 90 var vendors = ['moz', 'webkit', 'o', 'ms'] 91 var x 92 93 // Remove vendor prefixing if prefixed and break early if not 94 for (x = 0; x < vendors.length && !requestAnimationFrame; x += 1) { 95 requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'] 96 } 97 98 if (!requestAnimationFrame) { 99 log('setup', 'RequestAnimationFrame not supported') 100 } else { 101 // Firefox extension content-scripts have a globalThis object that is not the same as window. 102 // Binding `requestAnimationFrame` to window allows the function to work and prevents errors 103 // being thrown when run in that context, and should be a no-op in every other context. 104 requestAnimationFrame = requestAnimationFrame.bind(window) 105 } 106 } 107 108 function getMyID(iframeId) { 109 var retStr = 'Host page: ' + iframeId 110 111 if (window.top !== window.self) { 112 retStr = 113 window.parentIFrame && window.parentIFrame.getId 114 ? window.parentIFrame.getId() + ': ' + iframeId 115 : 'Nested host page: ' + iframeId 116 } 117 118 return retStr 119 } 120 121 function formatLogHeader(iframeId) { 122 return msgId + '[' + getMyID(iframeId) + ']' 123 } 124 125 function isLogEnabled(iframeId) { 126 return settings[iframeId] ? settings[iframeId].log : logEnabled 127 } 128 129 function log(iframeId, msg) { 130 output('log', iframeId, msg, isLogEnabled(iframeId)) 131 } 132 133 function info(iframeId, msg) { 134 output('info', iframeId, msg, isLogEnabled(iframeId)) 135 } 136 137 function warn(iframeId, msg) { 138 output('warn', iframeId, msg, true) 139 } 140 141 function output(type, iframeId, msg, enabled) { 142 if (true === enabled && 'object' === typeof window.console) { 143 // eslint-disable-next-line no-console 144 console[type](formatLogHeader(iframeId), msg) 145 } 146 } 147 148 function iFrameListener(event) { 149 function resizeIFrame() { 150 function resize() { 151 setSize(messageData) 152 setPagePosition(iframeId) 153 on('onResized', messageData) 154 } 155 156 ensureInRange('Height') 157 ensureInRange('Width') 158 159 syncResize(resize, messageData, 'init') 160 } 161 162 function processMsg() { 163 var data = msg.substr(msgIdLen).split(':') 164 var height = data[1] ? parseInt(data[1], 10) : 0 165 var iframe = settings[data[0]] && settings[data[0]].iframe 166 var compStyle = getComputedStyle(iframe) 167 168 return { 169 iframe: iframe, 170 id: data[0], 171 height: height + getPaddingEnds(compStyle) + getBorderEnds(compStyle), 172 width: data[2], 173 type: data[3] 174 } 175 } 176 177 function getPaddingEnds(compStyle) { 178 if (compStyle.boxSizing !== 'border-box') { 179 return 0 180 } 181 var top = compStyle.paddingTop ? parseInt(compStyle.paddingTop, 10) : 0 182 var bot = compStyle.paddingBottom 183 ? parseInt(compStyle.paddingBottom, 10) 184 : 0 185 return top + bot 186 } 187 188 function getBorderEnds(compStyle) { 189 if (compStyle.boxSizing !== 'border-box') { 190 return 0 191 } 192 var top = compStyle.borderTopWidth 193 ? parseInt(compStyle.borderTopWidth, 10) 194 : 0 195 var bot = compStyle.borderBottomWidth 196 ? parseInt(compStyle.borderBottomWidth, 10) 197 : 0 198 return top + bot 199 } 200 201 function ensureInRange(Dimension) { 202 var max = Number(settings[iframeId]['max' + Dimension]), 203 min = Number(settings[iframeId]['min' + Dimension]), 204 dimension = Dimension.toLowerCase(), 205 size = Number(messageData[dimension]) 206 207 log(iframeId, 'Checking ' + dimension + ' is in range ' + min + '-' + max) 208 209 if (size < min) { 210 size = min 211 log(iframeId, 'Set ' + dimension + ' to min value') 212 } 213 214 if (size > max) { 215 size = max 216 log(iframeId, 'Set ' + dimension + ' to max value') 217 } 218 219 messageData[dimension] = '' + size 220 } 221 222 function isMessageFromIFrame() { 223 function checkAllowedOrigin() { 224 function checkList() { 225 var i = 0, 226 retCode = false 227 228 log( 229 iframeId, 230 'Checking connection is from allowed list of origins: ' + 231 checkOrigin 232 ) 233 234 for (; i < checkOrigin.length; i++) { 235 if (checkOrigin[i] === origin) { 236 retCode = true 237 break 238 } 239 } 240 return retCode 241 } 242 243 function checkSingle() { 244 var remoteHost = settings[iframeId] && settings[iframeId].remoteHost 245 log(iframeId, 'Checking connection is from: ' + remoteHost) 246 return origin === remoteHost 247 } 248 249 return checkOrigin.constructor === Array ? checkList() : checkSingle() 250 } 251 252 var origin = event.origin, 253 checkOrigin = settings[iframeId] && settings[iframeId].checkOrigin 254 255 if (checkOrigin && '' + origin !== 'null' && !checkAllowedOrigin()) { 256 throw new Error( 257 'Unexpected message received from: ' + 258 origin + 259 ' for ' + 260 messageData.iframe.id + 261 '. Message was: ' + 262 event.data + 263 '. This error can be disabled by setting the checkOrigin: false option or by providing of array of trusted domains.' 264 ) 265 } 266 267 return true 268 } 269 270 function isMessageForUs() { 271 return ( 272 msgId === ('' + msg).substr(0, msgIdLen) && 273 msg.substr(msgIdLen).split(':')[0] in settings 274 ) // ''+Protects against non-string msg 275 } 276 277 function isMessageFromMetaParent() { 278 // Test if this message is from a parent above us. This is an ugly test, however, updating 279 // the message format would break backwards compatibility. 280 var retCode = messageData.type in { true: 1, false: 1, undefined: 1 } 281 282 if (retCode) { 283 log(iframeId, 'Ignoring init message from meta parent page') 284 } 285 286 return retCode 287 } 288 289 function getMsgBody(offset) { 290 return msg.substr(msg.indexOf(':') + msgHeaderLen + offset) 291 } 292 293 function forwardMsgFromIFrame(msgBody) { 294 log( 295 iframeId, 296 'onMessage passed: {iframe: ' + 297 messageData.iframe.id + 298 ', message: ' + 299 msgBody + 300 '}' 301 ) 302 303 on('onMessage', { 304 iframe: messageData.iframe, 305 message: JSON.parse(msgBody) 306 }) 307
308 log(iframeId, '--') 309 } 310 311 function getPageInfo() { 312 var bodyPosition = document.body.getBoundingClientRect(), 313 iFramePosition = messageData.iframe.getBoundingClientRect() 314 315 return JSON.stringify({ 316 iframeHeight: iFramePosition.height, 317 iframeWidth: iFramePosition.width, 318 clientHeight: Math.max( 319 document.documentElement.clientHeight, 320 window.innerHeight || 0 321 ), 322 clientWidth: Math.max( 323 document.documentElement.clientWidth, 324 window.innerWidth || 0 325 ), 326 offsetTop: parseInt(iFramePosition.top - bodyPosition.top, 10), 327 offsetLeft: parseInt(iFramePosition.left - bodyPosition.left, 10), 328 scrollTop: window.pageYOffset, 329 scrollLeft: window.pageXOffset, 330 documentHeight: document.documentElement.clientHeight, 331 documentWidth: document.documentElement.clientWidth, 332 windowHeight: window.innerHeight, 333 windowWidth: window.innerWidth 334 }) 335 } 336 337 function sendPageInfoToIframe(iframe, iframeId) { 338 function debouncedTrigger() { 339 trigger('Send Page Info', 'pageInfo:' + getPageInfo(), iframe, iframeId) 340 } 341 debounceFrameEvents(debouncedTrigger, 32, iframeId) 342 } 343 344 function startPageInfoMonitor() { 345 function setListener(type, func) { 346 function sendPageInfo() { 347 if (settings[id]) { 348 sendPageInfoToIframe(settings[id].iframe, id) 349 } else { 350 stop() 351 } 352 } 353 354 ;['scroll', 'resize'].forEach(function (evt) { 355 log(id, type + evt + ' listener for sendPageInfo') 356 func(window, evt, sendPageInfo) 357 }) 358 } 359 360 function stop() { 361 setListener('Remove ', removeEventListener) 362 } 363 364 function start() { 365 setListener('Add ', addEventListener) 366 } 367 368 var id = iframeId // Create locally scoped copy of iFrame ID 369 370 start() 371 372 if (settings[id]) { 373 settings[id].stopPageInfo = stop 374 } 375 } 376 377 function stopPageInfoMonitor() { 378 if (settings[iframeId] && settings[iframeId].stopPageInfo) { 379 settings[iframeId].stopPageInfo() 380 delete settings[iframeId].stopPageInfo 381 } 382 } 383 384 function checkIFrameExists() { 385 var retBool = true 386 387 if (null === messageData.iframe) { 388 warn(iframeId, 'IFrame (' + messageData.id + ') not found') 389 retBool = false 390 } 391 return retBool 392 } 393 394 function getElementPosition(target) { 395 var iFramePosition = target.getBoundingClientRect() 396 397 getPagePosition(iframeId) 398 399 return { 400 x: Math.floor(Number(iFramePosition.left) + Number(pagePosition.x)), 401 y: Math.floor(Number(iFramePosition.top) + Number(pagePosition.y)) 402 } 403 } 404 405 function scrollRequestFromChild(addOffset) { 406 /* istanbul ignore next */ // Not testable in Karma 407 function reposition() { 408 pagePosition = newPosition 409 scrollTo() 410 log(iframeId, '--') 411 } 412 413 function calcOffset() { 414 return { 415 x: Number(messageData.width) + offset.x, 416 y: Number(messageData.height) + offset.y 417 } 418 } 419 420 function scrollParent() { 421 if (window.parentIFrame) { 422 window.parentIFrame['scrollTo' + (addOffset ? 'Offset' : '')]( 423 newPosition.x, 424 newPosition.y 425 ) 426 } else { 427 warn( 428 iframeId, 429 'Unable to scroll to requested position, window.parentIFrame not found' 430 ) 431 } 432 } 433 434 var offset = addOffset 435 ? getElementPosition(messageData.iframe) 436 : { x: 0, y: 0 }, 437 newPosition = calcOffset() 438 439 log( 440 iframeId, 441 'Reposition requested from iFrame (offset x:' + 442 offset.x + 443 ' y:' + 444 offset.y + 445 ')' 446 ) 447 448 if (window.top !== window.self) { 449 scrollParent() 450 } else { 451 reposition() 452 } 453 } 454 455 function scrollTo() { 456 if (false !== on('onScroll', pagePosition)) { 457 setPagePosition(iframeId) 458 } else { 459 unsetPagePosition() 460 } 461 } 462 463 function findTarget(location) { 464 function jumpToTarget() { 465 var jumpPosition = getElementPosition(target) 466 467 log( 468 iframeId, 469 'Moving to in page link (#' + 470 hash + 471 ') at x: ' + 472 jumpPosition.x + 473 ' y: ' + 474 jumpPosition.y 475 ) 476 pagePosition = { 477 x: jumpPosition.x, 478 y: jumpPosition.y 479 } 480 481 scrollTo()
482 log(iframeId, '--') 483 } 484 485 function jumpToParent() { 486 if (window.parentIFrame) { 487 window.parentIFrame.moveToAnchor(hash) 488 } else { 489 log( 490 iframeId, 491 'In page link #' + 492 hash + 493 ' not found and window.parentIFrame not found' 494 ) 495 } 496 } 497 498 var hash = location.split('#')[1] || '', 499 hashData = decodeURIComponent(hash), 500 target = 501 document.getElementById(hashData) || 502 document.getElementsByName(hashData)[0] 503 504 if (target) { 505 jumpToTarget() 506 } else if (window.top !== window.self) { 507 jumpToParent() 508 } else { 509 log(iframeId, 'In page link #' + hash + ' not found') 510 } 511 } 512 513 function onMouse(event) { 514 var mousePos = {} 515 516 if (Number(messageData.width) === 0 && Number(messageData.height) === 0) { 517 var data = getMsgBody(9).split(':') 518 mousePos = { 519 x: data[1], 520 y: data[0] 521 } 522 } else { 523 mousePos = { 524 x: messageData.width, 525 y: messageData.height 526 } 527 } 528 529 on(event, { 530 iframe: messageData.iframe, 531 screenX: Number(mousePos.x), 532 screenY: Number(mousePos.y), 533 type: messageData.type 534 }) 535 } 536 537 function on(funcName, val) { 538 return chkEvent(iframeId, funcName, val) 539 } 540 541 function actionMsg() { 542 if (settings[iframeId] && settings[iframeId].firstRun) firstRun() 543 544 switch (messageData.type) { 545 case 'close': 546 closeIFrame(messageData.iframe) 547 break 548 549 case 'message': 550 forwardMsgFromIFrame(getMsgBody(6)) 551 break 552 553 case 'mouseenter': 554 onMouse('onMouseEnter') 555 break 556 557 case 'mouseleave': 558 onMouse('onMouseLeave') 559 break 560 561 case 'autoResize': 562 settings[iframeId].autoResize = JSON.parse(getMsgBody(9)) 563 break 564 565 case 'scrollTo': 566 scrollRequestFromChild(false) 567 break 568 569 case 'scrollToOffset': 570 scrollRequestFromChild(true) 571 break 572 573 case 'pageInfo': 574 sendPageInfoToIframe( 575 settings[iframeId] && settings[iframeId].iframe, 576 iframeId 577 ) 578 startPageInfoMonitor() 579 break 580 581 case 'pageInfoStop': 582 stopPageInfoMonitor() 583 break 584 585 case 'inPageLink': 586 findTarget(getMsgBody(9)) 587 break 588 589 case 'reset': 590 resetIFrame(messageData) 591 break 592 593 case 'init': 594 resizeIFrame() 595 on('onInit', messageData.iframe) 596 break 597 598 default: 599 if ( 600 Number(messageData.width) === 0 && 601 Number(messageData.height) === 0 602 ) { 603 warn( 604 'Unsupported message received (' + 605 messageData.type + 606 '), this is likely due to the iframe containing a later ' + 607 'version of iframe-resizer than the parent page' 608 ) 609 } else { 610 resizeIFrame() 611 } 612 } 613 } 614 615 function hasSettings(iframeId) { 616 var retBool = true 617 618 if (!settings[iframeId]) { 619 retBool = false 620 warn( 621 messageData.type + 622 ' No settings for ' + 623 iframeId + 624 '. Message was: ' + 625 msg 626 ) 627 } 628 629 return retBool 630 } 631 632 function iFrameReadyMsgReceived() { 633 // eslint-disable-next-line no-restricted-syntax, guard-for-in 634 for (var iframeId in settings) { 635 trigger( 636 'iFrame requested init', 637 createOutgoingMsg(iframeId), 638 settings[iframeId].iframe, 639 iframeId 640 ) 641 } 642 } 643 644 function firstRun() { 645 if (settings[iframeId]) { 646 settings[iframeId].firstRun = false 647 } 648 } 649 650 var msg = event.data, 651 messageData = {}, 652 iframeId = null 653 654 if ('[iFrameResizerChild]Ready' === msg) { 655 iFrameReadyMsgReceived() 656 } else if (isMessageForUs()) { 657 messageData = processMsg() 658 iframeId = messageData.id 659 if (settings[iframeId]) { 660 settings[iframeId].loaded = true 661 } 662 663 if (!isMessageFromMetaParent() && hasSettings(iframeId)) { 664 log(iframeId, 'Received: ' + msg) 665 666 if (checkIFrameExists() && isMessageFromIFrame()) { 667 actionMsg() 668 } 669 } 670 } else { 671 info(iframeId, 'Ignored: ' + msg) 672 } 673 } 674 675 function chkEvent(iframeId, funcName, val) { 676 var func = null, 677 retVal = null 678 679 if (settings[iframeId]) { 680 func = settings[iframeId][funcName] 681 682 if ('function' === typeof func) { 683 retVal = func(val) 684 } else { 685 throw new TypeError( 686 funcName + ' on iFrame[' + iframeId + '] is not a function' 687 ) 688 } 689 } 690 691 return retVal 692 } 693 694 function removeIframeListeners(iframe) { 695 var iframeId = iframe.id 696 delete settings[iframeId] 697 } 698 699 function closeIFrame(iframe) { 700 var iframeId = iframe.id 701 if (chkEvent(iframeId, 'onClose', iframeId) === false) { 702 log(iframeId, 'Close iframe cancelled by onClose event') 703 return 704 } 705 log(iframeId, 'Removing iFrame: ' + iframeId) 706 707 try { 708 // Catch race condition error with React 709 if (iframe.parentNode) { 710 iframe.parentNode.removeChild(iframe) 711 } 712 } catch (error) { 713 warn(error) 714 } 715 716 chkEvent(iframeId, 'onClosed', iframeId)
717 log(iframeId, '--') 718 removeIframeListeners(iframe) 719 } 720 721 function getPagePosition(iframeId) { 722 if (null === pagePosition) { 723 pagePosition = { 724 x: 725 window.pageXOffset !== undefined 726 ? window.pageXOffset 727 : document.documentElement.scrollLeft, 728 y: 729 window.pageYOffset !== undefined 730 ? window.pageYOffset 731 : document.documentElement.scrollTop 732 } 733 log( 734 iframeId, 735 'Get page position: ' + pagePosition.x + ',' + pagePosition.y 736 ) 737 } 738 } 739 740 function setPagePosition(iframeId) { 741 if (null !== pagePosition) { 742 window.scrollTo(pagePosition.x, pagePosition.y) 743 log( 744 iframeId, 745 'Set page position: ' + pagePosition.x + ',' + pagePosition.y 746 ) 747 unsetPagePosition() 748 } 749 } 750 751 function unsetPagePosition() { 752 pagePosition = null 753 } 754 755 function resetIFrame(messageData) { 756 function reset() { 757 setSize(messageData) 758 trigger('reset', 'reset', messageData.iframe, messageData.id) 759 } 760 761 log( 762 messageData.id, 763 'Size reset requested by ' + 764 ('init' === messageData.type ? 'host page' : 'iFrame') 765 ) 766 getPagePosition(messageData.id) 767 syncResize(reset, messageData, 'reset') 768 } 769 770 function setSize(messageData) { 771 function setDimension(dimension) { 772 if (!messageData.id) { 773 log('undefined', 'messageData id not set') 774 return 775 } 776 messageData.iframe.style[dimension] = messageData[dimension] + 'px' 777 log( 778 messageData.id, 779 'IFrame (' + 780 iframeId + 781 ') ' + 782 dimension + 783 ' set to ' + 784 messageData[dimension] + 785 'px' 786 ) 787 } 788 789 function chkZero(dimension) { 790 // FireFox sets dimension of hidden iFrames to zero. 791 // So if we detect that set up an event to check for 792 // when iFrame becomes visible. 793 794 /* istanbul ignore next */ // Not testable in PhantomJS 795 if (!hiddenCheckEnabled && '0' === messageData[dimension]) { 796 hiddenCheckEnabled = true 797 log(iframeId, 'Hidden iFrame detected, creating visibility listener') 798 fixHiddenIFrames() 799 } 800 } 801 802 function processDimension(dimension) { 803 setDimension(dimension) 804 chkZero(dimension) 805 } 806 807 var iframeId = messageData.iframe.id 808 809 if (settings[iframeId]) { 810 if (settings[iframeId].sizeHeight) { 811 processDimension('height') 812 } 813 if (settings[iframeId].sizeWidth) { 814 processDimension('width') 815 } 816 } 817 } 818 819 function syncResize(func, messageData, doNotSync) { 820 /* istanbul ignore if */ // Not testable in PhantomJS 821 if ( 822 doNotSync !== messageData.type && 823 requestAnimationFrame && 824 // including check for jasmine because had trouble getting spy to work in unit test using requestAnimationFrame 825 !window.jasmine 826 ) { 827 log(messageData.id, 'Requesting animation frame') 828 requestAnimationFrame(func) 829 } else { 830 func() 831 } 832 } 833 834 function trigger(calleeMsg, msg, iframe, id, noResponseWarning) { 835 function postMessageToIFrame() { 836 var target = settings[id] && settings[id].targetOrigin 837 log( 838 id, 839 '[' + 840 calleeMsg + 841 '] Sending msg to iframe[' + 842 id + 843 '] (' + 844 msg + 845 ') targetOrigin: ' + 846 target 847 ) 848 iframe.contentWindow.postMessage(msgId + msg, target) 849 } 850 851 function iFrameNotFound() { 852 warn(id, '[' + calleeMsg + '] IFrame(' + id + ') not found') 853 } 854 855 function chkAndSend() { 856 if ( 857 iframe && 858 'contentWindow' in iframe && 859 null !== iframe.contentWindow 860 ) { 861 // Null test for PhantomJS 862 postMessageToIFrame() 863 } else { 864 iFrameNotFound() 865 } 866 } 867 868 function warnOnNoResponse() { 869 function warning() {
870 if (settings[id] && !settings[id].loaded && !errorShown) { 871 errorShown = true 872 warn( 873 id, 874 'IFrame has not responded within ' + 875 settings[id].warningTimeout / 1000 + 876 ' seconds. Check iFrameResizer.contentWindow.js has been loaded in iFrame. This message can be ignored if everything is working, or you can set the warningTimeout option to a higher value or zero to suppress this warning.' 877 ) 878 } 879 } 880 881 if ( 882 !!noResponseWarning && 883 settings[id] && 884 !!settings[id].warningTimeout 885 ) { 886 settings[id].msgTimeout = setTimeout( 887 warning, 888 settings[id].warningTimeout 889 ) 890 } 891 } 892 893 var errorShown = false 894 895 id = id || iframe.id 896 897 if (settings[id]) { 898 chkAndSend() 899 warnOnNoResponse() 900 } 901 } 902 903 function createOutgoingMsg(iframeId) { 904 return ( 905 iframeId + 906 ':' + 907 settings[iframeId].bodyMarginV1 + 908 ':' + 909 settings[iframeId].sizeWidth + 910 ':' + 911 settings[iframeId].log + 912 ':' + 913 settings[iframeId].interval + 914 ':' + 915 settings[iframeId].enablePublicMethods + 916 ':' + 917 settings[iframeId].autoResize + 918 ':' + 919 settings[iframeId].bodyMargin + 920 ':' + 921 settings[iframeId].heightCalculationMethod + 922 ':' + 923 settings[iframeId].bodyBackground + 924 ':' + 925 settings[iframeId].bodyPadding + 926 ':' + 927 settings[iframeId].tolerance + 928 ':' + 929 settings[iframeId].inPageLinks + 930 ':' + 931 settings[iframeId].resizeFrom + 932 ':' + 933 settings[iframeId].widthCalculationMethod + 934 ':' + 935 settings[iframeId].mouseEvents 936 ) 937 } 938 939 function isNumber(value) { 940 return typeof value === 'number' 941 } 942 943 function setupIFrame(iframe, options) { 944 function setLimits() { 945 function addStyle(style) { 946 var styleValue = settings[iframeId][style] 947 if (Infinity !== styleValue && 0 !== styleValue) { 948 iframe.style[style] = isNumber(styleValue) 949 ? styleValue + 'px' 950 : styleValue 951 log(iframeId, 'Set ' + style + ' = ' + iframe.style[style]) 952 } 953 } 954 955 function chkMinMax(dimension) { 956 if ( 957 settings[iframeId]['min' + dimension] > 958 settings[iframeId]['max' + dimension] 959 ) { 960 throw new Error( 961 'Value for min' + 962 dimension + 963 ' can not be greater than max' + 964 dimension 965 ) 966 } 967 } 968 969 chkMinMax('Height') 970 chkMinMax('Width') 971 972 addStyle('maxHeight') 973 addStyle('minHeight') 974 addStyle('maxWidth') 975 addStyle('minWidth') 976 } 977 978 function newId() { 979 var id = (options && options.id) || defaults.id + count++ 980 if (null !== document.getElementById(id)) { 981 id += count++ 982 } 983 return id 984 } 985 986 function ensureHasId(iframeId) { 987 if ('' === iframeId) { 988 // eslint-disable-next-line no-multi-assign 989 iframe.id = iframeId = newId() 990 logEnabled = (options || {}).log 991 log( 992 iframeId, 993 'Added missing iframe ID: ' + iframeId + ' (' + iframe.src + ')' 994 ) 995 } 996 997 return iframeId 998 } 999 1000 function setScrolling() { 1001 log( 1002 iframeId, 1003 'IFrame scrolling ' + 1004 (settings[iframeId] && settings[iframeId].scrolling 1005 ? 'enabled' 1006 : 'disabled') + 1007 ' for ' + 1008 iframeId 1009 ) 1010 iframe.style.overflow = 1011 false === (settings[iframeId] && settings[iframeId].scrolling) 1012 ? 'hidden' 1013 : 'auto' 1014 switch (settings[iframeId] && settings[iframeId].scrolling) { 1015 case 'omit': 1016 break 1017 1018 case true: 1019 iframe.scrolling = 'yes' 1020 break 1021 1022 case false: 1023 iframe.scrolling = 'no' 1024 break 1025 1026 default: 1027 iframe.scrolling = settings[iframeId] 1028 ? settings[iframeId].scrolling 1029 : 'no' 1030 } 1031 } 1032 1033 // The V1 iFrame script expects an int, where as in V2 expects a CSS 1034 // string value such as '1px 3em', so if we have an int for V2, set V1=V2 1035 // and then convert V2 to a string PX value. 1036 function setupBodyMarginValues() { 1037 if ( 1038 'number' ===
1039 typeof (settings[iframeId] && settings[iframeId].bodyMargin) || 1040 '0' === (settings[iframeId] && settings[iframeId].bodyMargin) 1041 ) { 1042 settings[iframeId].bodyMarginV1 = settings[iframeId].bodyMargin 1043 settings[iframeId].bodyMargin = 1044 '' + settings[iframeId].bodyMargin + 'px' 1045 } 1046 } 1047 1048 function checkReset() { 1049 // Reduce scope of firstRun to function, because IE8's JS execution 1050 // context stack is borked and this value gets externally 1051 // changed midway through running this function!!! 1052 var firstRun = settings[iframeId] && settings[iframeId].firstRun, 1053 resetRequertMethod = 1054 settings[iframeId] && 1055 settings[iframeId].heightCalculationMethod in resetRequiredMethods 1056 1057 if (!firstRun && resetRequertMethod) { 1058 resetIFrame({ iframe: iframe, height: 0, width: 0, type: 'init' }) 1059 } 1060 } 1061 1062 function setupIFrameObject() { 1063 if (settings[iframeId]) { 1064 settings[iframeId].iframe.iFrameResizer = { 1065 close: closeIFrame.bind(null, settings[iframeId].iframe), 1066 1067 removeListeners: removeIframeListeners.bind( 1068 null, 1069 settings[iframeId].iframe 1070 ), 1071 1072 resize: trigger.bind( 1073 null, 1074 'Window resize', 1075 'resize', 1076 settings[iframeId].iframe 1077 ), 1078 1079 moveToAnchor: function (anchor) { 1080 trigger( 1081 'Move to anchor', 1082 'moveToAnchor:' + anchor, 1083 settings[iframeId].iframe, 1084 iframeId 1085 ) 1086 }, 1087 1088 sendMessage: function (message) { 1089 message = JSON.stringify(message) 1090 trigger( 1091 'Send Message', 1092 'message:' + message, 1093 settings[iframeId].iframe, 1094 iframeId 1095 ) 1096 } 1097 } 1098 } 1099 } 1100 1101 // We have to call trigger twice, as we can not be sure if all 1102 // iframes have completed loading when this code runs. The 1103 // event listener also catches the page changing in the iFrame. 1104 function init(msg) { 1105 function iFrameLoaded() { 1106 trigger('iFrame.onload', msg, iframe, undefined, true) 1107 checkReset() 1108 } 1109 1110 function createDestroyObserver(MutationObserver) { 1111 if (!iframe.parentNode) { 1112 return 1113 } 1114 1115 var destroyObserver = new MutationObserver(function (mutations) { 1116 mutations.forEach(function (mutation) { 1117 var removedNodes = Array.prototype.slice.call(mutation.removedNodes) // Transform NodeList into an Array 1118 removedNodes.forEach(function (removedNode) { 1119 if (removedNode === iframe) { 1120 closeIFrame(iframe) 1121 } 1122 }) 1123 }) 1124 }) 1125 destroyObserver.observe(iframe.parentNode, { 1126 childList: true 1127 }) 1128 } 1129 1130 var MutationObserver = getMutationObserver() 1131 if (MutationObserver) { 1132 createDestroyObserver(MutationObserver) 1133 } 1134 1135 addEventListener(iframe, 'load', iFrameLoaded) 1136 trigger('init', msg, iframe, undefined, true) 1137 } 1138 1139 function checkOptions(options) { 1140 if ('object' !== typeof options) { 1141 throw new TypeError('Options is not an object') 1142 } 1143 } 1144 1145 function copyOptions(options) { 1146 // eslint-disable-next-line no-restricted-syntax 1147 for (var option in defaults) { 1148 if (Object.prototype.hasOwnProperty.call(defaults, option)) { 1149 settings[iframeId][option] = Object.prototype.hasOwnProperty.call( 1150 options, 1151 option 1152 ) 1153 ? options[option] 1154 : defaults[option] 1155 } 1156 } 1157 } 1158 1159 function getTargetOrigin(remoteHost) { 1160 return '' === remoteHost || 1161 null !== remoteHost.match(/^(about:blank|javascript:|file:\/\/)/) 1162 ? '*' 1163 : remoteHost 1164 } 1165 1166 function depricate(key) { 1167 var splitName = key.split('Callback') 1168 1169 if (splitName.length === 2) { 1170 var name = 1171 'on' + splitName[0].charAt(0).toUpperCase() + splitName[0].slice(1) 1172 this[name] = this[key] 1173 delete this[key] 1174 warn( 1175 iframeId, 1176 "Deprecated: '" + 1177 key + 1178 "' has been renamed '" + 1179 name + 1180 "'. The old method will be removed in the next major version." 1181 ) 1182 } 1183 } 1184 1185 function processOptions(options) { 1186 options = options || {} 1187 settings[iframeId] = { 1188 firstRun: true, 1189 iframe: iframe,
1190 remoteHost: iframe.src && iframe.src.split('/').slice(0, 3).join('/') 1191 } 1192 1193 checkOptions(options) 1194 Object.keys(options).forEach(depricate, options) 1195 copyOptions(options) 1196 1197 if (settings[iframeId]) { 1198 settings[iframeId].targetOrigin = 1199 true === settings[iframeId].checkOrigin 1200 ? getTargetOrigin(settings[iframeId].remoteHost) 1201 : '*' 1202 } 1203 } 1204 1205 function beenHere() { 1206 return iframeId in settings && 'iFrameResizer' in iframe 1207 } 1208 1209 var iframeId = ensureHasId(iframe.id) 1210 1211 if (!beenHere()) { 1212 processOptions(options) 1213 setScrolling() 1214 setLimits() 1215 setupBodyMarginValues() 1216 init(createOutgoingMsg(iframeId)) 1217 setupIFrameObject() 1218 } else { 1219 warn(iframeId, 'Ignored iFrame, already setup.') 1220 } 1221 } 1222 1223 function debouce(fn, time) { 1224 if (null === timer) { 1225 timer = setTimeout(function () { 1226 timer = null 1227 fn() 1228 }, time) 1229 } 1230 } 1231 1232 var frameTimer = {} 1233 function debounceFrameEvents(fn, time, frameId) { 1234 if (!frameTimer[frameId]) { 1235 frameTimer[frameId] = setTimeout(function () { 1236 frameTimer[frameId] = null 1237 fn() 1238 }, time) 1239 } 1240 } 1241 1242 // Not testable in PhantomJS 1243 /* istanbul ignore next */ 1244 1245 function fixHiddenIFrames() { 1246 function checkIFrames() { 1247 function checkIFrame(settingId) { 1248 function chkDimension(dimension) { 1249 return ( 1250 '0px' === 1251 (settings[settingId] && settings[settingId].iframe.style[dimension]) 1252 ) 1253 } 1254 1255 function isVisible(el) { 1256 return null !== el.offsetParent 1257 } 1258 1259 if ( 1260 settings[settingId] && 1261 isVisible(settings[settingId].iframe) && 1262 (chkDimension('height') || chkDimension('width')) 1263 ) { 1264 trigger( 1265 'Visibility change', 1266 'resize', 1267 settings[settingId].iframe, 1268 settingId 1269 ) 1270 } 1271 } 1272 1273 Object.keys(settings).forEach(function (key) { 1274 checkIFrame(key) 1275 }) 1276 } 1277 1278 function mutationObserved(mutations) { 1279 // watch for changes in the inputs corresponding to the iframes 1280 mutations.forEach(mut=>{ 1281 // make sure this executes only on pages with a formidable form 1282 if ( window.frmrules && mut.target.classList?.contains('ssa_appointment_form_field_appointment_id') && !mut.target.value){ 1283 // scoped function to sync the iframe 1284 let syncIframe = () => { 1285 let src = jQuery(`#${mut.target.id}`).closest('div').find('iframe').attr('src') 1286 jQuery(`#${mut.target.id}`).closest('div').find('iframe').attr( 'src', '' ) 1287 debouce(()=>{ 1288 jQuery(`#${mut.target.id}`).closest('div').find('iframe').attr( 'src', src ) 1289 }, 100 ) 1290 } 1291 // debounce to space out the syncs and avoid crashing 1292 debouce(syncIframe, 16) 1293 } 1294 }) 1295 log( 1296 'window', 1297 'Mutation observed: ' + mutations[0].target + ' ' + mutations[0].type 1298 ) 1299 debouce(checkIFrames, 16) 1300 } 1301 1302 function createMutationObserver() { 1303 var target = document.querySelector('body'), 1304 config = { 1305 attributes: true, 1306 attributeOldValue: false, 1307 characterData: true, 1308 characterDataOldValue: false, 1309 childList: true, 1310 subtree: true 1311 }, 1312 observer = new MutationObserver(mutationObserved) 1313 1314 observer.observe(target, config) 1315 } 1316 1317 var MutationObserver = getMutationObserver() 1318 if (MutationObserver) { 1319 createMutationObserver() 1320 } 1321 } 1322 1323 function resizeIFrames(event) { 1324 function resize() { 1325 sendTriggerMsg('Window ' + event, 'resize') 1326 } 1327 1328 log('window', 'Trigger event: ' + event) 1329 debouce(resize, 16) 1330 } 1331 1332 // Not testable in PhantomJS 1333 /* istanbul ignore next */ 1334 function tabVisible() { 1335 function resize() { 1336 sendTriggerMsg('Tab Visible', 'resize') 1337 } 1338 1339 if ('hidden' !== document.visibilityState) { 1340 log('document', 'Trigger event: Visibility change') 1341 debouce(resize, 16) 1342 } 1343 } 1344 1345 function sendTriggerMsg(eventName, event) { 1346 function isIFrameResizeEnabled(iframeId) { 1347 return ( 1348 settings[iframeId] && 1349 'parent' === settings[iframeId].resizeFrom && 1350 settings[iframeId].autoResize &&
1351 !settings[iframeId].firstRun 1352 ) 1353 } 1354 1355 Object.keys(settings).forEach(function (iframeId) { 1356 if (isIFrameResizeEnabled(iframeId)) { 1357 trigger(eventName, event, settings[iframeId].iframe, iframeId) 1358 } 1359 }) 1360 } 1361 1362 function setupEventListeners() { 1363 addEventListener(window, 'message', iFrameListener) 1364 1365 addEventListener(window, 'resize', function () { 1366 resizeIFrames('resize') 1367 }) 1368 1369 } 1370 1371 function factory() { 1372 function init(options, element) { 1373 function chkType() { 1374 if (!element.tagName) { 1375 throw new TypeError('Object is not a valid DOM element') 1376 } else if ('IFRAME' !== element.tagName.toUpperCase()) { 1377 throw new TypeError( 1378 'Expected <IFRAME> tag, found <' + element.tagName + '>' 1379 ) 1380 } 1381 } 1382 1383 if (element) { 1384 chkType() 1385 setupIFrame(element, options) 1386 iFrames.push(element) 1387 } 1388 } 1389 1390 function warnDeprecatedOptions(options) { 1391 if (options && options.enablePublicMethods) { 1392 warn( 1393 'enablePublicMethods option has been removed, public methods are now always available in the iFrame' 1394 ) 1395 } 1396 } 1397 1398 var iFrames 1399 1400 setupRequestAnimationFrame() 1401 setupEventListeners() 1402 1403 return function iFrameResizeF(options, target) { 1404 iFrames = [] // Only return iFrames past in on this call 1405 1406 warnDeprecatedOptions(options) 1407 1408 switch (typeof target) { 1409 case 'undefined': 1410 case 'string': 1411 Array.prototype.forEach.call( 1412 document.querySelectorAll(target || 'iframe'), 1413 init.bind(undefined, options) 1414 ) 1415 break 1416 1417 case 'object': 1418 init(options, target) 1419 break 1420 1421 default: 1422 throw new TypeError('Unexpected data type (' + typeof target + ')') 1423 } 1424 1425 return iFrames 1426 } 1427 } 1428 1429 function createJQueryPublicMethod($) { 1430 if (!$.fn) { 1431 info('', 'Unable to bind to jQuery, it is not fully loaded.') 1432 } else if (!$.fn.iFrameResize) { 1433 $.fn.iFrameResize = function $iFrameResizeF(options) { 1434 function init(index, element) { 1435 setupIFrame(element, options) 1436 } 1437 1438 return this.filter('iframe').each(init).end() 1439 } 1440 } 1441 } 1442 1443 if (window.jQuery) { 1444 createJQueryPublicMethod(window.jQuery) 1445 } 1446 1447 if (typeof define === 'function' && define.amd) { 1448 define([], factory) 1449 } else if (typeof module === 'object' && typeof module.exports === 'object') { 1450 // Node for browserfy 1451 module.exports = factory() 1452 } 1453 window.iFrameResize = window.iFrameResize || factory() 1454})() 1455 1456/* ----- SSA Code --------- */ 1457 1458var bookingIframes = document.querySelectorAll('.ssa_booking_iframe'); 1459var iframeInteraction = false; // Track if the iframe has been activated 1460 1461var ssaDebouncedScroll = debounce(function(e){ // Debouncing scroll event listener to improve performance 1462 ssaHandleScroll(e); 1463}, 300); 1464 1465// Detect any interactivity with iframe - focus moved to any element inside 1466window.addEventListener('blur', function(){ 1467 let activeEl = document.activeElement; 1468 1469 if (activeEl.tagName === 'IFRAME' && activeEl.classList.contains('ssa_booking_iframe')) { 1470 iframeInteraction = true; 1471 } 1472}); 1473 1474// helper function to initialize the IframeResizer 1475var ssaInitIframeResizer = function() { 1476 var ssaIframeSettings = { 1477 heightCalculationMethod: 'bodyOffset', 1478 checkOrigin: false, 1479 warningTimeout: 20000, 1480 onResized: ssaDebouncedScroll, 1481 onScroll: false 1482 } 1483 1484 iFrameResize(ssaIframeSettings, '.ssa_booking_iframe') 1485} 1486 1487// call on page load if there are SSA iframes 1488if (bookingIframes) { 1489 ssaInitIframeResizer(); 1490} 1491 1492// Only run Elementor and FF code if jQuery is on the page 1493if ( window.jQuery ) { 1494 1495 // Elementor integration 1496 jQuery( document ).on( 'elementor/popup/show', function(){ 1497 ssaInitIframeResizer() 1498 }); 1499 1500 jQuery( document ).ready(function($){ 1501 1502 // Formidable Forms integration 1503 $(document).on( 'frmPageChanged', function( event, form, response ) { 1504 var $iframes = $( '.frm_forms .ssa_booking_iframe' ); 1505 1506 // if form page has changed, and the current page contains an SSA iframe, 1507 // initialize IframeResizer 1508 if( $iframes.length ) { 1509 ssaInitIframeResizer(); 1510 } 1511 }); 1512 1513 // Gravity Forms integration 1514 $( document ).on( 'gform_page_loaded', function( event, form_id, current_page ) { 1515 var $iframes = $( '.gform_wrapper .ssa_booking_iframe' ); 1516 1517 // if form page has changed, and the current page contains an SSA iframe, 1518 // initialize IframeResizer 1519 if( $iframes.length ) { 1520 ssaInitIframeResizer(); 1521 } 1522 }); 1523 }); 1524 1525} // end jQuery check 1526 1527// Returns a function, that, as long as it continues to be invoked, will not 1528// be triggered. The function will be called after it stops being called for 1529// N milliseconds. If `immediate` is passed, trigger the function on the 1530// leading edge, instead of the trailing. 1531function debounce(func, wait, immediate) { 1532 var timeout; 1533 return function() { 1534 var context = this, args = arguments; 1535 var later = function() { 1536 timeout = null;
1537 if (!immediate) func.apply(context, args); 1538 }; 1539 var callNow = immediate && !timeout; 1540 clearTimeout(timeout); 1541 timeout = setTimeout(later, wait); 1542 if (callNow) func.apply(context, args); 1543 }; 1544}; 1545 1546function ssaHandleScroll(e) { 1547 if (e.type !== 'mutationObserver' ) { // Only scroll if iframe DOM has mutated 1548 return; 1549 } 1550 1551 var rect = e.iframe.getBoundingClientRect(); 1552 var windowHeight = window.innerHeight || document.documentElement.clientHeight; 1553 1554 // is in viewPort 1555 var isInViewport = rect.bottom >= 0 && 1556 rect.right >= 0 && 1557 rect.top <= (window.innerHeight) && 1558 rect.left <= (window.innerWidth); 1559 1560 // is top of the iframe visible 1561 var isTopVisible = rect.top >= 0 && rect.top <= windowHeight; 1562 1563 // is bottom of the iframe visible 1564 var isBottomVisible = rect.bottom >= 0 && rect.bottom <= windowHeight; 1565 1566 if ( !isInViewport ) { 1567 return; 1568 } 1569 1570 if ( !iframeInteraction ) { 1571 return; 1572 } 1573 1574 if (isBottomVisible && isTopVisible) { 1575 // whole iframe is visible, do nothing 1576 return 1577 } 1578 1579 if ( rect.height > windowHeight ) { 1580 // top of the iframe is visible, scroll to the top of the iframe, 1581 if ( isTopVisible ) { 1582 e.iframe.scrollIntoView({ 1583 behavior: 'smooth', 1584 block: 'start' 1585 }); 1586 1587 // if the bottom of the iframe is visible, scroll to the bottom of the iframe 1588 } else if ( isBottomVisible ) { 1589 e.iframe.scrollIntoView({ 1590 behavior: 'smooth', 1591 block: 'end' 1592 }); 1593 } 1594 } else { 1595 // if the iframe is smaller than the viewport, scroll to the center of the iframe 1596 e.iframe.scrollIntoView({ 1597 behavior: 'smooth', 1598 block: 'center' 1599 }); 1600 return; 1601 } 1602} 1603
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.