(function (global)()

in src/js/iframeMessenger.js [9:502]


(function (global) {
    'use strict';
    /* global AdobeEdge */

    var iframeMessenger = (function() {
        var MutationObserver =  window.MutationObserver ||
                                window.WebKitMutationObserver ||
                                window.MozMutationObserver;
        var REFRESH_DELAY = 200;
        var MSG_ID_PREFIX = 'iframeMessenger';
        var _postMessageCallbacks = {};
        var _currentHeight;
        var _images = [];
        var _options = {
            absoluteHeight: false,
            enableUpdateInterval: true
        };

        /**
         * Send message to parent page and store callback ref if needed.
         * @param {oject} message Message to send to parent page.
         * @param {function} callback (optional) Func. to call upon return msg.
         */
        function _postMessage(message, callback) {
            var id = genID();
            message.id = id;

            // Store callback ready for post message event lookup
            if (callback) {
                _postMessageCallbacks[id] = callback;
            }

            window.parent.postMessage(JSON.stringify(message), '*');
        }

        /**
         * Generate a unique ID string using known prefix and random chars.
         * @returns {string} unique ID
         */
        function genID() {
            // Rnd logic from http://stackoverflow.com/a/8084248
            var rnd = Math.random().toString(36).substr(2, 5);
            return MSG_ID_PREFIX + ':' + rnd;
        }

        /**
         * Check if inside an iframe
         * @return {bool}
         */
        function _inIframe() {
            try {
                return window.self !== window.top;
            } catch (e) {
                return true;
            }
        }

        /**
         * Fix body margin and floats.
         */
        function fixBodyHeight() {
            var css = 'body::before, body::after{';
            css += 'content: ".";';
            css += 'height: 0;';
            css += 'margin: 0;';
            css += 'overflow: hidden;';
            css += 'visibility: hidden;';
            css += 'display: block;';
            css += 'clear: both;';
            css += '}';
            css += 'body{';
            css += 'margin: 0 !important;';
            css += 'display: inline-block !important;';
            css += 'float: left !important;';
            css += 'width: 100% !important;';
            css += 'box-sizing: border-box !important;';
            css += '}';

            var head = document.querySelector('head');
            var styleEl = document.createElement('style');
            styleEl.appendChild(document.createTextNode(css));
            head.appendChild(styleEl);
        }


        /**
         * Navigate parent page to new URL.
         * @param  {string} url New URL location.
         */
        function navigate(url) {
            var message = {
                type:'navigate',
                value: url
            };

            _postMessage(message);
        }


        /**
         * Resize the iframe element
         * @param  {number|string} (optional) height Can be number or percentage
         */
        function resize(height) {
            // If no height is provided use page height
            if (typeof height === 'undefined') {
                _handleResize();
            } else {
                _sendHeight(height);
            }
        }

        /**
         * Run function on load or immediately if ready
         */
        function _onLoad(fn) {
            if (document.readyState !== 'complete') {
                window.addEventListener('load', fn, false);
            } else {
                fn();
            }
        }

        /**
         * Send height.
         * @param  {number|string} height Can be number or percentage.
         */
        function _sendHeight(height) {
            var message = {
                type:'set-height',
                value: height
            };
            // For AMP pages iframes must send a specific message for resizing
            var ampMessage = {
                sentinel: 'amp',
                type: 'embed-size',
                height: height
            };

            _postMessage(message);
            _postMessage(ampMessage);
        }

        /**
         * Get the current document height.
         * @return {int} Height integer
         */
        function _getHeight() {
            var height = parseInt(document.body.offsetHeight, 10);
            var styles = document.defaultView.getComputedStyle(document.body);
            height += parseInt(styles.getPropertyValue('margin-bottom'), 10);
            height += parseInt(styles.getPropertyValue('margin-top'), 10);
            return height;
        }

        /**
         * Get the bottom most position of all elements on the page.
         * NOTE: Absolute positioned elements are removed from the page flow
         *       so are not included in the .innerHeight of the page.
         *       Therefore, the height is retrieved via looping through
         *       every element and finding the one with greatest bounding
         *       bottom value. There is a performance hit relative to the number
         *       of elements on the page.
         *
         * @return {int} Greatest value of an elements bounding bottom.
         */
        function _getAbsoluteHeight() {
            var allElements = document.querySelectorAll('body *');
            var maxBottomVal = 0;

            Array.prototype.forEach.call(allElements, function(el) {
                var styles = window.getComputedStyle(el);
                var marginBottom = 0;
                if (styles.marginBottom &&
                    !isNaN(parseInt(styles.marginBottom), 10))
                {
                    marginBottom = parseInt(styles.marginBottom, 10);
                }

                var posBottom = el.getBoundingClientRect().bottom;
                var elBottom = marginBottom + posBottom;

                if (elBottom > maxBottomVal) {
                    maxBottomVal = elBottom;
                }
            });

            return Math.ceil(maxBottomVal);
        }

        /**
         * Handle document size change, send containing iframe a postMessage.
         */
        function _handleResize() {
            var newHeight;
            if (_options.absoluteHeight) {
                newHeight = _getAbsoluteHeight();
            } else {
                newHeight = _getHeight();
            }

            if (_currentHeight === newHeight) {
                return;
            }

            _sendHeight(newHeight);
            _currentHeight = newHeight;
        }

        /**
         * Interval resize loop.
         */
        function _setupInterval() {
            setInterval(_handleResize, REFRESH_DELAY);
        }


        /**
         * Listen for DOM modifications.
         */
        function _setupMutationObserver() {
            var target = document.querySelector('body');
            var observer = new MutationObserver(function() {
                _addImageLoadListeners();
                _handleResize();
             });

            var config = {
                attributes: true,
                childList: true,
                characterData: true,
                subtree: true
            };

            observer.observe(target, config);
        }

        /**
         * Start listening to resize events and trigger a resize.
         */
        function enableAutoResize(options) {
            // Apply options
            for (var key in options) {
                if (options.hasOwnProperty(key)) {
                    _options[key] = options[key];
                }
            }

            // Adobe edge interactives use transform:scale()
            // Hook into their bootstrap and wait before resizing
            if (typeof AdobeEdge !== 'undefined' &&
                typeof AdobeEdge.bootstrapCallback !== 'undefined') {
                AdobeEdge.bootstrapCallback(_setupAutoResize);
            } else {
                _onLoad(_setupAutoResize)
            }
        }

        function _setupAutoResize() {
            window.addEventListener('resize', _handleResize);
            _addImageLoadListeners();

            // Check for DOM changes
            if (MutationObserver) {
                _setupMutationObserver();
            } else if (_options.enableUpdateInterval === true) {
                _setupInterval();
            }

            _handleResize();
        }


        /**
         * Handle postMessage response and trigger callback with data.
         * @param  {object} event Post message event object
         */
        function _handlePostMessage(event) {
            if (event.data) {
                var data;
                try {
                    data = JSON.parse(event.data);
                } catch(err) {
                    return console.log(
                        'iframeMessenger: Error parsing data. ' + err.toString()
                    );
                }

                // Check postmessage is expected
                if (data.hasOwnProperty('id') &&
                    _postMessageCallbacks.hasOwnProperty(data.id))
                {
                    // Run callback with data
                    _postMessageCallbacks[data.id](data);

                    // If subscribe is true, then assume the callback can be
                    // called an indefinite number of times. Otherwise, assume this
                    // was a one-time request for data which has now been fulfilled.
                    if (!data.subscribe) {
                        delete _postMessageCallbacks[data.id];
                    }
                }
            }
        }


        /**
         * Get positional information from parent page.
         * @param  {Function} callback Callback to be trigger on response.
         */
        function getPositionInformation(callback) {
            _postMessage({
                type:'get-position'
            }, callback);
        }

        /**
         * Get parent window location information.
         * @param {Function} callback Callback to be trigger on response.
         */
        function getLocation(callback) {
            _postMessage({
                type: 'get-location'
            }, callback);
        }


        /**
         * Scroll the parent document to a specified position
         * @param  {int} _x X position.
         * @param  {int} _y Y position.
         */
        function scrollTo(_x, _y) {
            _postMessage({
                type:'scroll-to',
                x: _x,
                y: _y
            });
        }

        /**
         * Tell the parent to monitor the position of the iframe container
         */
        function monitorPosition(callback) {
            _postMessage({
                type: 'monitor-position'
            }, callback);
        }

        /**
         * Prep-page for messaging.
         */
        function _setupPage() {
            _addImageLoadListeners();
            fixBodyHeight();

            // IE9+ as IE8 does not support getComputedStyle
            if (!document.body || !getComputedStyle) {
                return;
            }

            document.documentElement.style.height = '';
            document.body.style.height = '';

            // Fix Chrome's scrollbar
            document.querySelector('html').style.overflow = 'hidden';
        }

        /**
         * Handle image load by triggering a resize
         */
        function _imageLoaded(event) {
            if (!event || event.type !== 'load') {
                return;
            }

            var img = event.srcElement;
            // Remove image from loading stack
            var imageIndex = _images.indexOf(img);
            if (imageIndex !== -1) {
                _images.splice(imageIndex, 1);
            }

            // Calculate new height
            _handleResize();
        }

        /**
         * Add 'load' event listener and store reference to image
         * @param {elm} img Image being loaded
         */
        function _addImage(img) {
            if (_images.indexOf(img) === -1) {
                img.addEventListener('load', _imageLoaded);
                _images.push(img);
            }
        }


        /**
         * Filter out images in the DOM that haven't loaded yet and add listener
         */
        function _addImageLoadListeners() {
            var imgs = document.querySelectorAll('img');
            for (var i = 0; i < imgs.length; i++) {
                var image = imgs[i];
                // Do nothing if image is already loaded
                if (image.nodeName === 'IMG' &&
                    image.src &&
                    image.complete === false &&
                    image.readyState !== 4)
                {
                    _addImage(image);
                }
            }
        }

        function _addAcquisitionDataToLinks(acquisitionData) {
            var links = document.getElementsByClassName('js-acquisition-link');
            var json = encodeURIComponent(JSON.stringify(acquisitionData));
            for (var i = 0; i < links.length; i++) {
                var link = links[i];
                var href = link.getAttribute('href');
                if (href) {
                    href += href.indexOf('?') === -1 ? '?' : '&';
                    href += 'acquisitionData=';
                    href += json;
                    link.setAttribute('href', href);                    
                }
            }
        }

        function _buildAcquisitionData(acquisitionData, referrerData) {
            if (acquisitionData && referrerData) {
                return {
                    componentId: acquisitionData.componentId,
                    componentType: acquisitionData.componentType,
                    source: acquisitionData.source,
                    abTest: acquisitionData.abTest,
                    campaignCode: acquisitionData.campaignCode,
                    referrerPageviewId: referrerData.referrerPageviewId,
                    referrerUrl: referrerData.referrerUrl
                }
            }
            return null;
        }

        function _enrichAcquisitionLinks(acquisitionData) {
            var message = { type: 'enrich-acquisition-links' };
            _postMessage(message, function(data) {
                var referrerData = data.referrerData;
                if (referrerData && data.type === message.type) {
                    var updatedAcquisitionData = _buildAcquisitionData(acquisitionData, referrerData);
                    if (updatedAcquisitionData) {
                        _addAcquisitionDataToLinks(updatedAcquisitionData);
                    }
                }
            });
        }

        // Only setup the page if inside an iframe
        if (_inIframe()) {
            _onLoad(_setupPage);
            window.addEventListener('message', _handlePostMessage, false);
        }

        return {
            resize: resize,
            navigate: navigate,
            enableAutoResize: enableAutoResize,
            scrollTo: scrollTo,
            getLocation: getLocation,
            getAbsoluteHeight: _getAbsoluteHeight,
            getPositionInformation: getPositionInformation,
            monitorPosition: monitorPosition,
            enrichAcquisitionLinks: _enrichAcquisitionLinks
        };
    }());

    // CommonJS module
    if ( typeof module !== 'undefined' && module.exports ) {
        module.exports = iframeMessenger;
    }

    // AMD module
    else if ( typeof define !== 'undefined' && define.amd ) {
        define( function () { return iframeMessenger; });
    }

    // browser global
    else {
        global.iframeMessenger = iframeMessenger;
    }
}((typeof window !== 'undefined') ? window : this ));