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 'stripeModalOpened': 594 ssaDebouncedScroll(messageData) 595 596 case 'init': 597 resizeIFrame() 598 on('onInit', messageData.iframe) 599 break 600 601 default: 602 if ( 603 Number(messageData.width) === 0 && 604 Number(messageData.height) === 0 605 ) { 606 warn( 607 'Unsupported message received (' + 608 messageData.type + 609 '), this is likely due to the iframe containing a later ' + 610 'version of iframe-resizer than the parent page' 611 ) 612 } else { 613 resizeIFrame() 614 } 615 } 616 } 617 618 function hasSettings(iframeId) { 619 var retBool = true 620 621 if (!settings[iframeId]) { 622 retBool = false 623 warn( 624 messageData.type + 625 ' No settings for ' + 626 iframeId + 627 '. Message was: ' + 628 msg 629 ) 630 } 631 632 return retBool 633 } 634 635 function iFrameReadyMsgReceived() { 636 // eslint-disable-next-line no-restricted-syntax, guard-for-in 637 for (var iframeId in settings) { 638 trigger( 639 'iFrame requested init', 640 createOutgoingMsg(iframeId), 641 settings[iframeId].iframe, 642 iframeId 643 ) 644 } 645 } 646 647 function firstRun() { 648 if (settings[iframeId]) { 649 settings[iframeId].firstRun = false 650 } 651 } 652 653 var msg = event.data, 654 messageData = {}, 655 iframeId = null 656 657 if ('[iFrameResizerChild]Ready' === msg) { 658 iFrameReadyMsgReceived() 659 } else if (isMessageForUs()) { 660 messageData = processMsg() 661 iframeId = messageData.id 662 if (settings[iframeId]) { 663 settings[iframeId].loaded = true 664 } 665 666 if (!isMessageFromMetaParent() && hasSettings(iframeId)) { 667 log(iframeId, 'Received: ' + msg) 668 669 if (checkIFrameExists() && isMessageFromIFrame()) { 670 actionMsg() 671 } 672 } 673 } else { 674 info(iframeId, 'Ignored: ' + msg) 675 } 676 } 677 678 function chkEvent(iframeId, funcName, val) { 679 var func = null, 680 retVal = null 681 682 if (settings[iframeId]) { 683 func = settings[iframeId][funcName] 684 685 if ('function' === typeof func) { 686 retVal = func(val) 687 } else { 688 throw new TypeError( 689 funcName + ' on iFrame[' + iframeId + '] is not a function' 690 ) 691 } 692 } 693 694 return retVal 695 } 696 697 function removeIframeListeners(iframe) { 698 var iframeId = iframe.id 699 delete settings[iframeId] 700 } 701 702 function closeIFrame(iframe) { 703 var iframeId = iframe.id 704 if (chkEvent(iframeId, 'onClose', iframeId) === false) { 705 log(iframeId, 'Close iframe cancelled by onClose event') 706 return 707 } 708 log(iframeId, 'Removing iFrame: ' + iframeId) 709 710 try { 711 // Catch race condition error with React 712 if (iframe.parentNode) { 713 iframe.parentNode.removeChild(iframe) 714 } 715 } catch (error) { 716 warn(error) 717 } 718 719 chkEvent(iframeId, 'onClosed', iframeId)
720 log(iframeId, '--') 721 removeIframeListeners(iframe) 722 } 723 724 function getPagePosition(iframeId) { 725 if (null === pagePosition) { 726 pagePosition = { 727 x: 728 window.pageXOffset !== undefined 729 ? window.pageXOffset 730 : document.documentElement.scrollLeft, 731 y: 732 window.pageYOffset !== undefined 733 ? window.pageYOffset 734 : document.documentElement.scrollTop 735 } 736 log( 737 iframeId, 738 'Get page position: ' + pagePosition.x + ',' + pagePosition.y 739 ) 740 } 741 } 742 743 function setPagePosition(iframeId) { 744 if (null !== pagePosition) { 745 window.scrollTo(pagePosition.x, pagePosition.y) 746 log( 747 iframeId, 748 'Set page position: ' + pagePosition.x + ',' + pagePosition.y 749 ) 750 unsetPagePosition() 751 } 752 } 753 754 function unsetPagePosition() { 755 pagePosition = null 756 } 757 758 function resetIFrame(messageData) { 759 function reset() { 760 setSize(messageData) 761 trigger('reset', 'reset', messageData.iframe, messageData.id) 762 } 763 764 log( 765 messageData.id, 766 'Size reset requested by ' + 767 ('init' === messageData.type ? 'host page' : 'iFrame') 768 ) 769 getPagePosition(messageData.id) 770 syncResize(reset, messageData, 'reset') 771 } 772 773 function setSize(messageData) { 774 function setDimension(dimension) { 775 if (!messageData.id) { 776 log('undefined', 'messageData id not set') 777 return 778 } 779 messageData.iframe.style[dimension] = messageData[dimension] + 'px' 780 log( 781 messageData.id, 782 'IFrame (' + 783 iframeId + 784 ') ' + 785 dimension + 786 ' set to ' + 787 messageData[dimension] + 788 'px' 789 ) 790 } 791 792 function chkZero(dimension) { 793 // FireFox sets dimension of hidden iFrames to zero. 794 // So if we detect that set up an event to check for 795 // when iFrame becomes visible. 796 797 /* istanbul ignore next */ // Not testable in PhantomJS 798 if (!hiddenCheckEnabled && '0' === messageData[dimension]) { 799 hiddenCheckEnabled = true 800 log(iframeId, 'Hidden iFrame detected, creating visibility listener') 801 fixHiddenIFrames() 802 } 803 } 804 805 function processDimension(dimension) { 806 setDimension(dimension) 807 chkZero(dimension) 808 } 809 810 var iframeId = messageData.iframe.id 811 812 if (settings[iframeId]) { 813 if (settings[iframeId].sizeHeight) { 814 processDimension('height') 815 } 816 if (settings[iframeId].sizeWidth) { 817 processDimension('width') 818 } 819 } 820 } 821 822 function syncResize(func, messageData, doNotSync) { 823 /* istanbul ignore if */ // Not testable in PhantomJS 824 if ( 825 doNotSync !== messageData.type && 826 requestAnimationFrame && 827 // including check for jasmine because had trouble getting spy to work in unit test using requestAnimationFrame 828 !window.jasmine 829 ) { 830 log(messageData.id, 'Requesting animation frame') 831 requestAnimationFrame(func) 832 } else { 833 func() 834 } 835 } 836 837 function trigger(calleeMsg, msg, iframe, id, noResponseWarning) { 838 function postMessageToIFrame() { 839 var target = settings[id] && settings[id].targetOrigin 840 log( 841 id, 842 '[' + 843 calleeMsg + 844 '] Sending msg to iframe[' + 845 id + 846 '] (' + 847 msg + 848 ') targetOrigin: ' + 849 target 850 ) 851 iframe.contentWindow.postMessage(msgId + msg, target) 852 } 853 854 function iFrameNotFound() { 855 warn(id, '[' + calleeMsg + '] IFrame(' + id + ') not found') 856 } 857 858 function chkAndSend() { 859 if ( 860 iframe && 861 'contentWindow' in iframe && 862 null !== iframe.contentWindow 863 ) { 864 // Null test for PhantomJS 865 postMessageToIFrame() 866 } else { 867 iFrameNotFound() 868 } 869 } 870 871 function warnOnNoResponse() { 872 function warning() {
873 if (settings[id] && !settings[id].loaded && !errorShown) { 874 errorShown = true 875 warn( 876 id, 877 'IFrame has not responded within ' + 878 settings[id].warningTimeout / 1000 + 879 ' 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.' 880 ) 881 } 882 } 883 884 if ( 885 !!noResponseWarning && 886 settings[id] && 887 !!settings[id].warningTimeout 888 ) { 889 settings[id].msgTimeout = setTimeout( 890 warning, 891 settings[id].warningTimeout 892 ) 893 } 894 } 895 896 var errorShown = false 897 898 id = id || iframe.id 899 900 if (settings[id]) { 901 chkAndSend() 902 warnOnNoResponse() 903 } 904 } 905 906 function createOutgoingMsg(iframeId) { 907 return ( 908 iframeId + 909 ':' + 910 settings[iframeId].bodyMarginV1 + 911 ':' + 912 settings[iframeId].sizeWidth + 913 ':' + 914 settings[iframeId].log + 915 ':' + 916 settings[iframeId].interval + 917 ':' + 918 settings[iframeId].enablePublicMethods + 919 ':' + 920 settings[iframeId].autoResize + 921 ':' + 922 settings[iframeId].bodyMargin + 923 ':' + 924 settings[iframeId].heightCalculationMethod + 925 ':' + 926 settings[iframeId].bodyBackground + 927 ':' + 928 settings[iframeId].bodyPadding + 929 ':' + 930 settings[iframeId].tolerance + 931 ':' + 932 settings[iframeId].inPageLinks + 933 ':' + 934 settings[iframeId].resizeFrom + 935 ':' + 936 settings[iframeId].widthCalculationMethod + 937 ':' + 938 settings[iframeId].mouseEvents 939 ) 940 } 941 942 function isNumber(value) { 943 return typeof value === 'number' 944 } 945 946 function setupIFrame(iframe, options) { 947 function setLimits() { 948 function addStyle(style) { 949 var styleValue = settings[iframeId][style] 950 if (Infinity !== styleValue && 0 !== styleValue) { 951 iframe.style[style] = isNumber(styleValue) 952 ? styleValue + 'px' 953 : styleValue 954 log(iframeId, 'Set ' + style + ' = ' + iframe.style[style]) 955 } 956 } 957 958 function chkMinMax(dimension) { 959 if ( 960 settings[iframeId]['min' + dimension] > 961 settings[iframeId]['max' + dimension] 962 ) { 963 throw new Error( 964 'Value for min' + 965 dimension + 966 ' can not be greater than max' + 967 dimension 968 ) 969 } 970 } 971 972 chkMinMax('Height') 973 chkMinMax('Width') 974 975 addStyle('maxHeight') 976 addStyle('minHeight') 977 addStyle('maxWidth') 978 addStyle('minWidth') 979 } 980 981 function newId() { 982 var id = (options && options.id) || defaults.id + count++ 983 if (null !== document.getElementById(id)) { 984 id += count++ 985 } 986 return id 987 } 988 989 function ensureHasId(iframeId) { 990 if ('' === iframeId) { 991 // eslint-disable-next-line no-multi-assign 992 iframe.id = iframeId = newId() 993 logEnabled = (options || {}).log 994 log( 995 iframeId, 996 'Added missing iframe ID: ' + iframeId + ' (' + iframe.src + ')' 997 ) 998 } 999 1000 return iframeId 1001 } 1002 1003 function setScrolling() { 1004 log( 1005 iframeId, 1006 'IFrame scrolling ' + 1007 (settings[iframeId] && settings[iframeId].scrolling 1008 ? 'enabled' 1009 : 'disabled') + 1010 ' for ' + 1011 iframeId 1012 ) 1013 iframe.style.overflow = 1014 false === (settings[iframeId] && settings[iframeId].scrolling) 1015 ? 'hidden' 1016 : 'auto' 1017 switch (settings[iframeId] && settings[iframeId].scrolling) { 1018 case 'omit': 1019 break 1020 1021 case true: 1022 iframe.scrolling = 'yes' 1023 break 1024 1025 case false: 1026 iframe.scrolling = 'no' 1027 break 1028 1029 default: 1030 iframe.scrolling = settings[iframeId] 1031 ? settings[iframeId].scrolling 1032 : 'no' 1033 } 1034 } 1035 1036 // The V1 iFrame script expects an int, where as in V2 expects a CSS 1037 // string value such as '1px 3em', so if we have an int for V2, set V1=V2 1038 // and then convert V2 to a string PX value. 1039 function setupBodyMarginValues() { 1040 if ( 1041 'number' ===
1042 typeof (settings[iframeId] && settings[iframeId].bodyMargin) || 1043 '0' === (settings[iframeId] && settings[iframeId].bodyMargin) 1044 ) { 1045 settings[iframeId].bodyMarginV1 = settings[iframeId].bodyMargin 1046 settings[iframeId].bodyMargin = 1047 '' + settings[iframeId].bodyMargin + 'px' 1048 } 1049 } 1050 1051 function checkReset() { 1052 // Reduce scope of firstRun to function, because IE8's JS execution 1053 // context stack is borked and this value gets externally 1054 // changed midway through running this function!!! 1055 var firstRun = settings[iframeId] && settings[iframeId].firstRun, 1056 resetRequertMethod = 1057 settings[iframeId] && 1058 settings[iframeId].heightCalculationMethod in resetRequiredMethods 1059 1060 if (!firstRun && resetRequertMethod) { 1061 resetIFrame({ iframe: iframe, height: 0, width: 0, type: 'init' }) 1062 } 1063 } 1064 1065 function setupIFrameObject() { 1066 if (settings[iframeId]) { 1067 settings[iframeId].iframe.iFrameResizer = { 1068 close: closeIFrame.bind(null, settings[iframeId].iframe), 1069 1070 removeListeners: removeIframeListeners.bind( 1071 null, 1072 settings[iframeId].iframe 1073 ), 1074 1075 resize: trigger.bind( 1076 null, 1077 'Window resize', 1078 'resize', 1079 settings[iframeId].iframe 1080 ), 1081 1082 moveToAnchor: function (anchor) { 1083 trigger( 1084 'Move to anchor', 1085 'moveToAnchor:' + anchor, 1086 settings[iframeId].iframe, 1087 iframeId 1088 ) 1089 }, 1090 1091 sendMessage: function (message) { 1092 message = JSON.stringify(message) 1093 trigger( 1094 'Send Message', 1095 'message:' + message, 1096 settings[iframeId].iframe, 1097 iframeId 1098 ) 1099 } 1100 } 1101 } 1102 } 1103 1104 // We have to call trigger twice, as we can not be sure if all 1105 // iframes have completed loading when this code runs. The 1106 // event listener also catches the page changing in the iFrame. 1107 function init(msg) { 1108 function iFrameLoaded() { 1109 trigger('iFrame.onload', msg, iframe, undefined, true) 1110 checkReset() 1111 } 1112 1113 function createDestroyObserver(MutationObserver) { 1114 if (!iframe.parentNode) { 1115 return 1116 } 1117 1118 var destroyObserver = new MutationObserver(function (mutations) { 1119 mutations.forEach(function (mutation) { 1120 var removedNodes = Array.prototype.slice.call(mutation.removedNodes) // Transform NodeList into an Array 1121 removedNodes.forEach(function (removedNode) { 1122 if (removedNode === iframe) { 1123 closeIFrame(iframe) 1124 } 1125 }) 1126 }) 1127 }) 1128 destroyObserver.observe(iframe.parentNode, { 1129 childList: true 1130 }) 1131 } 1132 1133 var MutationObserver = getMutationObserver() 1134 if (MutationObserver) { 1135 createDestroyObserver(MutationObserver) 1136 } 1137 1138 addEventListener(iframe, 'load', iFrameLoaded) 1139 trigger('init', msg, iframe, undefined, true) 1140 } 1141 1142 function checkOptions(options) { 1143 if ('object' !== typeof options) { 1144 throw new TypeError('Options is not an object') 1145 } 1146 } 1147 1148 function copyOptions(options) { 1149 // eslint-disable-next-line no-restricted-syntax 1150 for (var option in defaults) { 1151 if (Object.prototype.hasOwnProperty.call(defaults, option)) { 1152 settings[iframeId][option] = Object.prototype.hasOwnProperty.call( 1153 options, 1154 option 1155 ) 1156 ? options[option] 1157 : defaults[option] 1158 } 1159 } 1160 } 1161 1162 function getTargetOrigin(remoteHost) { 1163 return '' === remoteHost || 1164 null !== remoteHost.match(/^(about:blank|javascript:|file:\/\/)/) 1165 ? '*' 1166 : remoteHost 1167 } 1168 1169 function depricate(key) { 1170 var splitName = key.split('Callback') 1171 1172 if (splitName.length === 2) { 1173 var name = 1174 'on' + splitName[0].charAt(0).toUpperCase() + splitName[0].slice(1) 1175 this[name] = this[key] 1176 delete this[key] 1177 warn( 1178 iframeId, 1179 "Deprecated: '" + 1180 key + 1181 "' has been renamed '" + 1182 name + 1183 "'. The old method will be removed in the next major version." 1184 ) 1185 } 1186 } 1187 1188 function processOptions(options) { 1189 options = options || {} 1190 settings[iframeId] = { 1191 firstRun: true, 1192 iframe: iframe,
1193 remoteHost: iframe.src && iframe.src.split('/').slice(0, 3).join('/') 1194 } 1195 1196 checkOptions(options) 1197 Object.keys(options).forEach(depricate, options) 1198 copyOptions(options) 1199 1200 if (settings[iframeId]) { 1201 settings[iframeId].targetOrigin = 1202 true === settings[iframeId].checkOrigin 1203 ? getTargetOrigin(settings[iframeId].remoteHost) 1204 : '*' 1205 } 1206 } 1207 1208 function beenHere() { 1209 return iframeId in settings && 'iFrameResizer' in iframe 1210 } 1211 1212 var iframeId = ensureHasId(iframe.id) 1213 1214 if (!beenHere()) { 1215 processOptions(options) 1216 setScrolling() 1217 setLimits() 1218 setupBodyMarginValues() 1219 init(createOutgoingMsg(iframeId)) 1220 setupIFrameObject() 1221 } else { 1222 warn(iframeId, 'Ignored iFrame, already setup.') 1223 } 1224 } 1225 1226 function debouce(fn, time) { 1227 if (null === timer) { 1228 timer = setTimeout(function () { 1229 timer = null 1230 fn() 1231 }, time) 1232 } 1233 } 1234 1235 var frameTimer = {} 1236 function debounceFrameEvents(fn, time, frameId) { 1237 if (!frameTimer[frameId]) { 1238 frameTimer[frameId] = setTimeout(function () { 1239 frameTimer[frameId] = null 1240 fn() 1241 }, time) 1242 } 1243 } 1244 1245 // Not testable in PhantomJS 1246 /* istanbul ignore next */ 1247 1248 function fixHiddenIFrames() { 1249 function checkIFrames() { 1250 function checkIFrame(settingId) { 1251 function chkDimension(dimension) { 1252 return ( 1253 '0px' === 1254 (settings[settingId] && settings[settingId].iframe.style[dimension]) 1255 ) 1256 } 1257 1258 function isVisible(el) { 1259 return null !== el.offsetParent 1260 } 1261 1262 if ( 1263 settings[settingId] && 1264 isVisible(settings[settingId].iframe) && 1265 (chkDimension('height') || chkDimension('width')) 1266 ) { 1267 trigger( 1268 'Visibility change', 1269 'resize', 1270 settings[settingId].iframe, 1271 settingId 1272 ) 1273 } 1274 } 1275 1276 Object.keys(settings).forEach(function (key) { 1277 checkIFrame(key) 1278 }) 1279 } 1280 1281 function mutationObserved(mutations) { 1282 // watch for changes in the inputs corresponding to the iframes 1283 mutations.forEach(mut=>{ 1284 // make sure this executes only on pages with a formidable form 1285 if ( window.frmrules && mut.target.classList?.contains('ssa_appointment_form_field_appointment_id') && !mut.target.value){ 1286 // scoped function to sync the iframe 1287 let syncIframe = () => { 1288 let src = jQuery(`#${mut.target.id}`).closest('div').find('iframe').attr('src') 1289 jQuery(`#${mut.target.id}`).closest('div').find('iframe').attr( 'src', '' ) 1290 debouce(()=>{ 1291 jQuery(`#${mut.target.id}`).closest('div').find('iframe').attr( 'src', src ) 1292 }, 100 ) 1293 } 1294 // debounce to space out the syncs and avoid crashing 1295 debouce(syncIframe, 16) 1296 } 1297 }) 1298 log( 1299 'window', 1300 'Mutation observed: ' + mutations[0].target + ' ' + mutations[0].type 1301 ) 1302 debouce(checkIFrames, 16) 1303 } 1304 1305 function createMutationObserver() { 1306 var target = document.querySelector('body'), 1307 config = { 1308 attributes: true, 1309 attributeOldValue: false, 1310 characterData: true, 1311 characterDataOldValue: false, 1312 childList: true, 1313 subtree: true 1314 }, 1315 observer = new MutationObserver(mutationObserved) 1316 1317 observer.observe(target, config) 1318 } 1319 1320 var MutationObserver = getMutationObserver() 1321 if (MutationObserver) { 1322 createMutationObserver() 1323 } 1324 } 1325 1326 function resizeIFrames(event) { 1327 function resize() { 1328 sendTriggerMsg('Window ' + event, 'resize') 1329 } 1330 1331 log('window', 'Trigger event: ' + event) 1332 debouce(resize, 16) 1333 } 1334 1335 // Not testable in PhantomJS 1336 /* istanbul ignore next */ 1337 function tabVisible() { 1338 function resize() { 1339 sendTriggerMsg('Tab Visible', 'resize') 1340 } 1341 1342 if ('hidden' !== document.visibilityState) { 1343 log('document', 'Trigger event: Visibility change') 1344 debouce(resize, 16) 1345 } 1346 } 1347 1348 function sendTriggerMsg(eventName, event) { 1349 function isIFrameResizeEnabled(iframeId) { 1350 return ( 1351 settings[iframeId] && 1352 'parent' === settings[iframeId].resizeFrom && 1353 settings[iframeId].autoResize &&
1354 !settings[iframeId].firstRun 1355 ) 1356 } 1357 1358 Object.keys(settings).forEach(function (iframeId) { 1359 if (isIFrameResizeEnabled(iframeId)) { 1360 trigger(eventName, event, settings[iframeId].iframe, iframeId) 1361 } 1362 }) 1363 } 1364 1365 function setupEventListeners() { 1366 addEventListener(window, 'message', iFrameListener) 1367 1368 addEventListener(window, 'resize', function () { 1369 resizeIFrames('resize') 1370 }) 1371 1372 } 1373 1374 function factory() { 1375 function init(options, element) { 1376 function chkType() { 1377 if (!element.tagName) { 1378 throw new TypeError('Object is not a valid DOM element') 1379 } else if ('IFRAME' !== element.tagName.toUpperCase()) { 1380 throw new TypeError( 1381 'Expected <IFRAME> tag, found <' + element.tagName + '>' 1382 ) 1383 } 1384 } 1385 1386 if (element) { 1387 chkType() 1388 setupIFrame(element, options) 1389 iFrames.push(element) 1390 } 1391 } 1392 1393 function warnDeprecatedOptions(options) { 1394 if (options && options.enablePublicMethods) { 1395 warn( 1396 'enablePublicMethods option has been removed, public methods are now always available in the iFrame' 1397 ) 1398 } 1399 } 1400 1401 var iFrames 1402 1403 setupRequestAnimationFrame() 1404 setupEventListeners() 1405 1406 return function iFrameResizeF(options, target) { 1407 iFrames = [] // Only return iFrames past in on this call 1408 1409 warnDeprecatedOptions(options) 1410 1411 switch (typeof target) { 1412 case 'undefined': 1413 case 'string': 1414 Array.prototype.forEach.call( 1415 document.querySelectorAll(target || 'iframe'), 1416 init.bind(undefined, options) 1417 ) 1418 break 1419 1420 case 'object': 1421 init(options, target) 1422 break 1423 1424 default: 1425 throw new TypeError('Unexpected data type (' + typeof target + ')') 1426 } 1427 1428 return iFrames 1429 } 1430 } 1431 1432 function createJQueryPublicMethod($) { 1433 if (!$.fn) { 1434 info('', 'Unable to bind to jQuery, it is not fully loaded.') 1435 } else if (!$.fn.iFrameResize) { 1436 $.fn.iFrameResize = function $iFrameResizeF(options) { 1437 function init(index, element) { 1438 setupIFrame(element, options) 1439 } 1440 1441 return this.filter('iframe').each(init).end() 1442 } 1443 } 1444 } 1445 1446 if (window.jQuery) { 1447 createJQueryPublicMethod(window.jQuery) 1448 } 1449 1450 if (typeof define === 'function' && define.amd) { 1451 define([], factory) 1452 } else if (typeof module === 'object' && typeof module.exports === 'object') { 1453 // Node for browserfy 1454 module.exports = factory() 1455 } 1456 window.iFrameResize = window.iFrameResize || factory() 1457})() 1458 1459/* ----- SSA Code --------- */ 1460 1461var bookingIframes = document.querySelectorAll('.ssa_booking_iframe'); 1462var iframeInteraction = false; // Track if the iframe has been activated 1463 1464var ssaDebouncedScroll = debounce(function(e){ // Debouncing scroll event listener to improve performance 1465 ssaHandleScroll(e); 1466}, 300); 1467 1468// Detect any interactivity with iframe - focus moved to any element inside 1469window.addEventListener('blur', function(){ 1470 let activeEl = document.activeElement; 1471 1472 if (activeEl.tagName === 'IFRAME' && activeEl.classList.contains('ssa_booking_iframe')) { 1473 iframeInteraction = true; 1474 } 1475}); 1476 1477// helper function to initialize the IframeResizer 1478var ssaInitIframeResizer = function() { 1479 var ssaIframeSettings = { 1480 heightCalculationMethod: 'taggedElement', 1481 checkOrigin: false, 1482 warningTimeout: 20000, 1483 onResized: ssaDebouncedScroll, 1484 onScroll: false 1485 } 1486 1487 iFrameResize(ssaIframeSettings, '.ssa_booking_iframe') 1488} 1489 1490// call on page load if there are SSA iframes 1491if (bookingIframes) { 1492 ssaInitIframeResizer(); 1493} 1494 1495// Only run Elementor and FF code if jQuery is on the page 1496if ( window.jQuery ) { 1497 1498 // Elementor integration 1499 jQuery( document ).on( 'elementor/popup/show', function(){ 1500 ssaInitIframeResizer() 1501 }); 1502 1503 jQuery( document ).ready(function($){ 1504 1505 // Formidable Forms integration 1506 $(document).on( 'frmPageChanged', function( event, form, response ) { 1507 var $iframes = $( '.frm_forms .ssa_booking_iframe' ); 1508 1509 // if form page has changed, and the current page contains an SSA iframe, 1510 // initialize IframeResizer 1511 if( $iframes.length ) { 1512 ssaInitIframeResizer(); 1513 } 1514 }); 1515 1516 // Gravity Forms integration 1517 $( document ).on( 'gform_page_loaded', function( event, form_id, current_page ) { 1518 var $iframes = $( '.gform_wrapper .ssa_booking_iframe' ); 1519 1520 // if form page has changed, and the current page contains an SSA iframe, 1521 // initialize IframeResizer 1522 if( $iframes.length ) { 1523 ssaInitIframeResizer(); 1524 } 1525 }); 1526 }); 1527 1528} // end jQuery check 1529 1530// Returns a function, that, as long as it continues to be invoked, will not 1531// be triggered. The function will be called after it stops being called for 1532// N milliseconds. If `immediate` is passed, trigger the function on the 1533// leading edge, instead of the trailing. 1534function debounce(func, wait, immediate) { 1535 var timeout; 1536 return function() { 1537 var context = this, args = arguments; 1538 var later = function() { 1539 timeout = null;
1540 if (!immediate) func.apply(context, args); 1541 }; 1542 var callNow = immediate && !timeout; 1543 clearTimeout(timeout); 1544 timeout = setTimeout(later, wait); 1545 if (callNow) func.apply(context, args); 1546 }; 1547}; 1548 1549function ssaHandleScroll(e) { 1550 if (e.type === 'stripeModalOpened') { 1551 e.iframe.scrollIntoView({ 1552 behavior: 'smooth', 1553 block: 'end' 1554 }); 1555 return; 1556 } 1557 1558 // This is needed for a bug on mobile devices where events are constantly emitted from the iframe and not all of them are mutationObserver 1559 if (e.type !== 'mutationObserver' ) { // Only scroll if iframe DOM has mutated 1560 return; 1561 } 1562 1563 var rect = e.iframe.getBoundingClientRect(); 1564 var windowHeight = window.innerHeight || document.documentElement.clientHeight; 1565 1566 // is in viewPort 1567 var isInViewport = rect.bottom >= 0 && 1568 rect.right >= 0 && 1569 rect.top <= (window.innerHeight) && 1570 rect.left <= (window.innerWidth); 1571 1572 // is top of the iframe visible 1573 var isTopVisible = rect.top >= 0 && rect.top <= windowHeight; 1574 1575 // is bottom of the iframe visible 1576 var isBottomVisible = rect.bottom >= 0 && rect.bottom <= windowHeight; 1577 1578 if ( !isInViewport ) { 1579 return; 1580 } 1581 1582 if ( !iframeInteraction ) { 1583 return; 1584 } 1585 1586 if (isBottomVisible && isTopVisible) { 1587 // whole iframe is visible, do nothing 1588 return 1589 } 1590 1591 if ( rect.height > windowHeight ) { 1592 // top of the iframe is visible, scroll to the top of the iframe, 1593 if ( isTopVisible ) { 1594 e.iframe.scrollIntoView({ 1595 behavior: 'smooth', 1596 block: 'start' 1597 }); 1598 1599 // if the bottom of the iframe is visible, scroll to the bottom of the iframe 1600 } else if ( isBottomVisible ) { 1601 e.iframe.scrollIntoView({ 1602 behavior: 'smooth', 1603 block: 'end' 1604 }); 1605 } 1606 } else { 1607 // if the iframe is smaller than the viewport, scroll to the center of the iframe 1608 e.iframe.scrollIntoView({ 1609 behavior: 'smooth', 1610 block: 'center' 1611 }); 1612 return; 1613 } 1614} 1615
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.