LivePersonInc/chronosjs

View on GitHub
src/courier/PostMessageChannel.js

Summary

Maintainability
F
4 days
Test Coverage
;(function (root, chronosRoot, factory) {
    "use strict";

    /* istanbul ignore if  */
    //<amd>
    if ("function" === typeof define && define.amd) {

        // AMD. Register as an anonymous module.
        define("Chronos.PostMessageChannel", ["Chronos.PostMessageUtilities", "Chronos.PostMessageChannelPolyfill"], function (PostMessageUtilities, PostMessageChannelPolyfill) {
            return factory(root, chronosRoot, PostMessageUtilities, PostMessageChannelPolyfill, true);
        });
        return;
    }
    //</amd>
    /* istanbul ignore next  */
    if ("object" !== typeof exports) {
        /**
         * @depend ./PostMessageUtilities.js
         * @depend ./PostMessageChannelPolyfill.js
         */
        chronosRoot.Chronos = chronosRoot.Chronos || {};
        factory(root, chronosRoot.Chronos, chronosRoot.Chronos.PostMessageUtilities, chronosRoot.Chronos.PostMessageChannelPolyfill);
    }
}(this, typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, PostMessageUtilities, PostMessageChannelPolyfill, hide) {
    "use strict";

    /*jshint validthis:true */
    var IFRAME_PREFIX = "LPFRM";
    var TOKEN_PREFIX = "LPTKN";
    var HANSHAKE_PREFIX = "HNDSK";
    var DEFAULT_CONCURRENCY = 100;
    var DEFAULT_HANDSHAKE_RETRY_INTERVAL = 5000;
    var DEFAULT_HANDSHAKE_RETRY_ATTEMPTS = 3;
    var DEFAULT_BODY_LOAD_DELAY = 100;

    /**
     * PostMessageChannel constructor
     * @constructor
     * @param {Object} options the configuration options for the instance
     * @param {Object} options.target - the target iframe or iframe configuration
     * @param {String} [options.target.url] - the url to load
     * @param {Object} [options.target.container] - the container in which the iframe should be created (if not supplied, document.body will be used)
     * @param {String} [options.target.style] - the CSS style to apply
     * @param {String} [options.target.style.width] width of iframe
     * @param {String} [options.target.style.height] height of iframe
     *          .....
     * @param {Boolean} [options.target.bust = true] - optional flag to indicate usage of cache buster when loading the iframe (default to true)
     * @param {Function} [options.target.callback] - a callback to invoke after the iframe had been loaded,
     * @param {Object} [options.target.context] - optional context for the callback
     * @param {Function|Object} [options.onready] - optional data for usage when iframe had been loaded {
     * @param {Function} [options.onready.callback] - a callback to invoke after the iframe had been loaded
     * @param {Object} [options.onready.context] - optional context for the callback
     * @param {Boolean} [options.removeDispose] - optional flag for removal of the iframe on dispose
     * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
     * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
     * @param {String} [options.targetOrigin] optional targetOrigin to be used when posting the message (must be supplied in case of external iframe)
     * @param {Number} [options.maxConcurrency = 100] - optional maximum concurrency that can be managed by the component before dropping
     * @param {Number} [options.handshakeInterval = 5000] - optional handshake interval for retries
     * @param {Number} [options.handshakeAttempts = 3] - optional number of retries handshake attempts
     * @param {String} [options.hostParam] - optional parameter of the host parameter name (default is lpHost)
     * @param {Function} onmessage - the handler for incoming messages
     */
    function PostMessageChannel(options, onmessage) {
        /* istanbul ignore if  */
        // For forcing new keyword
        if (false === (this instanceof PostMessageChannel)) {
            return new PostMessageChannel(options, onmessage);
        }

        this.initialize(options, onmessage);
    }

    PostMessageChannel.prototype = (function () {
        /**
         * Method for initialization
         * @param {Object} options the configuration options for the instance
         * @param {Object} options.target - the target iframe or iframe configuration
         * @param {String} [options.target.url] - the url to load
         * @param {Object} [options.target.container] - the container in which the iframe should be created (if not supplied, document.body will be used)
         * @param {String} [options.target.style] - the CSS style to apply
         * @param {String} [options.target.style.width] width of iframe
         * @param {String} [options.target.style.height] height of iframe
         *          .....
         * @param {Boolean} [options.target.bust = true] - optional flag to indicate usage of cache buster when loading the iframe (default to true)
         * @param {Function} [options.target.callback] - a callback to invoke after the iframe had been loaded,
         * @param {Object} [options.target.context] - optional context for the callback
         * @param {Function|Object} [options.onready] - optional data for usage when iframe had been loaded {
         * @param {Function} [options.onready.callback] - a callback to invoke after the iframe had been loaded
         * @param {Object} [options.onready.context] - optional context for the callback
         * @param {Boolean} [options.removeDispose] - optional flag for removal of the iframe on dispose
         * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
         * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
         * @param {String} [options.targetOrigin] optional targetOrigin to be used when posting the message (must be supplied in case of external iframe)
         * @param {Number} [options.maxConcurrency = 100] - optional maximum concurrency that can be managed by the component before dropping
         * @param {Number} [options.handshakeInterval = 5000] - optional handshake interval for retries
         * @param {Number} [options.handshakeAttempts = 3] - optional number of retries handshake attempts
         * @param {String} [options.hostParam] - optional parameter of the host parameter name (default is lpHost)
         * @param {Function} onmessage - the handler for incoming messages
         */
        function initialize(options, onmessage) {
            var handleMessage;
            var handler;

            if (!this.initialized) {
                this.hosted = false;
                this.messageQueue = [];

                options = options || {};
                handler = _initParameters.call(this, options, onmessage);
                if (!_isNativeMessageChannelSupported.call(this)) {
                    this.receiver = new PostMessageChannelPolyfill(this.target, {
                        serialize: this.serialize,
                        deserialize: this.deserialize
                    });
                    this.receiver.onmessage = handler;
                }

                if (this.hosted || !_isNativeMessageChannelSupported.call(this)) {
                    handleMessage = _getHandleMessage(handler).bind(this);
                    this.removeListener = PostMessageUtilities.addEventListener(root, "message", handleMessage);
                }
                else if (_isNativeMessageChannelSupported.call(this)) {
                    this.channelFactory();
                }

                if (this.target && !this.loading && !this.ready) {
                    _kickStartHandshake.call(this, handler, handleMessage);
                }

                this.initialized = true;
            }
        }

        /**
         * Method for removing the handler
         * @param {String} name - a name of the reference which holds the remove handler on this context,
         * @param {Boolean} ignore - optional flag to indicate whether to ignore the execution of the remove handler
         *
         */
        function _removeHandler(name, ignore) {
            // Remove handler if needed
            var func = PostMessageUtilities.parseFunction(this[name]);
            if (func) {
                if (!ignore) {
                    func.call(this);
                }

                this[name] = void 0;
                delete this[name];
            }
        }

        /**
         * Method for removing the timer
         */
        function _removeTimer(ignore) {
            // Remove timer if needed
            _removeHandler.call(this, "rmtimer", ignore);
        }

        /**
         * Method for removing the timer
         */
        function _removeLoadedHandler(ignore) {
            // Remove load handler if needed
            _removeHandler.call(this, "rmload", ignore);
        }

        /**
         * Method for disposing the object
         */
        function dispose() {
            if (!this.disposed) {
                if (this.removeListener) {
                    this.removeListener.call(this);
                    this.removeListener = void 0;
                }

                if (this.targetUrl && this.target || this.removeDispose) {
                    try {
                        if (this.targetContainer) {
                            this.targetContainer.removeChild(this.target);
                        }
                        else {
                            document.body.removeChild(this.target);
                        }
                    }
                    catch(ex) {
                        /* istanbul ignore next  */
                        PostMessageUtilities.log("Error while trying to remove the iframe from the container", "ERROR", "PostMessageChannel");
                    }
                }

                // Remove load handler if needed
                _removeTimer.call(this);

                // Remove timer if needed
                _removeLoadedHandler.call(this);

                this.messageQueue.length = 0;
                this.messageQueue = void 0;
                this.channel = void 0;
                this.onready = void 0;
                this.disposed = true;
            }
        }

        /**
         * Method to post the message to the target
         * @param {Object} message - the message to post
         * @param {Object} [target] - optional target for post
         * @param {Boolean} [force = false] - force post even if not ready
         */
        function postMessage(message, target, force) {
            var consumer;
            var parsed;

            if (!this.disposed) {
                try {
                    if (message) {
                        if (this.ready || force) {
                            // Post the message
                            consumer = target || this.receiver;
                            parsed = _prepareMessage.call(this, message);
                            consumer.postMessage(parsed);
                            return true;
                        }
                        else if (this.maxConcurrency >= this.messageQueue.length) {
                            // Need to delay/queue messages till target is ready
                            this.messageQueue.push(message);
                            return true;
                        }
                        else {
                            return false;
                        }
                    }
                }
                catch(ex) {
                    /* istanbul ignore next  */
                    PostMessageUtilities.log("Error while trying to post the message", "ERROR", "PostMessageChannel");
                    return false;
                }
            }
        }

        function _kickStartHandshake(handler, handleMessage) {
            var initiated;
            try {
                initiated = _handshake.call(this);
            }
            catch (ex) {
                initiated = false;
            }

            if (!initiated) {
                // Fallback to pure postMessage
                this.channel = false;
                this.receiver = new PostMessageChannelPolyfill(this.target, {
                    serialize: this.serialize,
                    deserialize: this.deserialize
                });
                this.receiver.onmessage = handler;

                if (!this.hosted) {
                    handleMessage = _getHandleMessage(handler).bind(this);
                    this.removeListener = PostMessageUtilities.addEventListener(root, "message", handleMessage);
                }

                _handshake.call(this);
            }

            this.handshakeAttempts--;

            PostMessageUtilities.delay(function () {
                if (!this.disposed && !this.hosted && !this.ready) {
                    this.rmload = _addLoadHandler.call(this, this.target);
                    this.rmtimer = PostMessageUtilities.delay(_handshake.bind(this, this.handshakeInterval), this.handshakeInterval);
                }
            }.bind(this));
        }

        function _initParameters(options, onmessage) {
            var handler;
            _simpleParametersInit.call(this, options);
            handler = _wrapMessageHandler(onmessage).bind(this);

            this.channelFactory = _hookupMessageChannel.call(this, handler);

            // No Iframe - We are inside it (hosted) initialized by the host/container
            if (!options.target || (options.target !== root || options.target === root.top) && "undefined" !== typeof Window && options.target instanceof Window) {
                this.hosted = true;
                this.target = options.target || root.top;
            }
            else if (options.target.contentWindow) { // We've got a reference to an "external" iframe
                this.target = options.target;
            }
            else if (options.target.url) { // We've got the needed configuration for creating an iframe
                this.targetUrl = options.target.url;
                this.targetOrigin = this.targetOrigin || PostMessageUtilities.getHost(options.target.url);
            }

            if (!this.hosted) {
                this.token = PostMessageUtilities.createUniqueSequence(TOKEN_PREFIX + PostMessageUtilities.SEQUENCE_FORMAT);
            }

            if (this.targetUrl) { // We've got the needed configuration for creating an iframe
                this.loading = true;
                this.targetContainer = options.target.container || document.body;
                this.target = _createIFrame.call(this, options.target, this.targetContainer);
            }
            return handler;
        }

        function _simpleParametersInit(options) {
            this.serialize = PostMessageUtilities.parseFunction(options.serialize, PostMessageUtilities.stringify);
            this.deserialize = PostMessageUtilities.parseFunction(options.deserialize, JSON.parse);
            this.targetOrigin = options.targetOrigin;
            this.maxConcurrency = PostMessageUtilities.parseNumber(options.maxConcurrency, DEFAULT_CONCURRENCY);
            this.handshakeInterval = PostMessageUtilities.parseNumber(options.handshakeInterval, DEFAULT_HANDSHAKE_RETRY_INTERVAL);
            this.handshakeAttemptsOrig = PostMessageUtilities.parseNumber(options.handshakeAttempts, DEFAULT_HANDSHAKE_RETRY_ATTEMPTS);
            this.handshakeAttempts = this.handshakeAttemptsOrig;
            this.hostParam = options.hostParam;
            this.channel = "undefined" !== typeof options.channel ? options.channel : _getChannelUrlIndicator();
            this.useObjects = options.useObjects;
            this.onready = _wrapReadyCallback(options.onready, options.target).bind(this);
            this.removeDispose = options.removeDispose;
        }

        /**
         * Method for handling the initial handler binding for needed event listeners
         * @param {Object} handler - the event object on message
         */
        function _getHandleMessage(handler) {
            return function(event) {
                var handshake;
                var previous;

                if (event.ports && 0 < event.ports.length) {
                    this.receiver = event.ports[0];

                    if (_isHandshake.call(this, event)) {
                        if (!this.token) {
                            this.token = event.data;
                        }
                    }

                    this.receiver.start();

                    // Swap Listeners
                    previous = this.removeListener.bind(this);
                    this.removeListener = PostMessageUtilities.addEventListener(this.receiver, "message", handler);
                    previous();

                    if (!this.disposed && this.hosted && !this.ready) {
                        handshake = true;
                    }
                }
                else {
                    if (_isHandshake.call(this, event)) {
                        if (!this.token) {
                            this.token = event.data;
                        }

                        if (!this.disposed && this.hosted && !this.ready) {
                            handshake = true;
                        }
                    }
                    else if (this.token) {
                        this.receiver.receive.call(this.receiver, event);
                    }
                }

                if (handshake) {
                    this.receiver.postMessage(HANSHAKE_PREFIX + this.token);
                    _onReady.call(this);
                }
            };
        }

        /**
         * Method to prepare the message for posting to the target
         * @param message
         * @returns {*}
         * @private
         */
        function _prepareMessage(message) {
            _tokenize.call(this, message);
            return this.serialize(message);
        }

        /**
         * Method to get url indication for using message channel or polyfill
         * @returns {Boolean} indication for message channel usage
         * @private
         */
        /* istanbul ignore next: it is being covered at the iframe side - cannot add it to coverage matrix  */
        function _getChannelUrlIndicator() {
            if ("true" === PostMessageUtilities.getURLParameter("lpPMCPolyfill")) {
                return false;
            }
        }

        /**
         * Method to create and hookup message channel factory for further use
         * @param {Function} onmessage - the message handler to be used with the channel
         * @private
         */
        function _hookupMessageChannel(onmessage) {
            return function() {
                this.channel = new MessageChannel();
                this.receiver = this.channel.port1;
                this.dispatcher = this.channel.port2;
                this.receiver.onmessage = onmessage;
                this.neutered = false;
            }.bind(this);
        }

        /**
         * Method for applying the token if any on the message
         * @param {Object} message - the message to be tokenize
         * @private
         */
        function _tokenize(message) {
            if (this.token) {
                message.token = this.token;
            }
        }

        /**
         * Method for applying the token if any on the message
         * @param {Object} message - the message to be tokenize
         * @private
         */
        function _validateToken(message) {
            return (message && message.token === this.token);
        }

        /**
         * Method to validate whether an event is for handshake
         * @param {Object} event - the event object on message
         * @private
         */
        function _isHandshake(event) {
            return (event && event.data && "string" === typeof event.data && (0 === event.data.indexOf(TOKEN_PREFIX) || (HANSHAKE_PREFIX + this.token) === event.data));
        }

        /**
         * Method for wrapping the callback of iframe ready
         * @param {Function} [onready] - the handler for iframe ready
         * @param {Object} [target] - the target iframe configuration
         * @returns {Function} handler function for messages
         * @private
         */
        function _wrapReadyCallback(onready, target) {
            return function(err) {
                if (target && "function" === typeof target.callback) {
                    target.callback.call(target.context, err, this.target);
                }
                if (onready) {
                    if ("function" === typeof onready) {
                        onready(err, this.target);
                    }
                    else if ("function" === typeof onready.callback) {
                        onready.callback.call(onready.context, err, this.target);
                    }
                }
            };
        }

        /**
         * Method for wrapping the handler of the postmessage for parsing
         * @param {Function} onmessage - the handler for incoming messages to invoke
         * @returns {Function} handler function for messages
         * @private
         */
        function _wrapMessageHandler(onmessage) {
            return function(message) {
                var msgObject;

                if (!message.origin || "*" === message.origin ||  this.targetOrigin === message.origin) {
                    if (_isHandshake.call(this, message) && !this.disposed && !this.hosted && !this.ready) {
                        _onReady.call(this);
                    }
                    else {
                        try {
                            msgObject = this.deserialize(message.data);

                            if (_validateToken.call(this, msgObject)) {
                                return onmessage && onmessage(msgObject);
                            }
                        }
                        catch (ex) {
                            msgObject = message.data || message;
                            PostMessageUtilities.log("Error while trying to handle the message", "ERROR", "PostMessageChannel");
                        }

                        return msgObject || message;
                    }
                }
            };
        }

        /**
         * Method to check whether the browser supports MessageChannel natively
         * @returns {Boolean} support flag
         * @private
         */
        function _isNativeMessageChannelSupported() {
            return false !== this.channel && "undefined" !== typeof MessageChannel && "undefined" !== typeof MessagePort;
        }

        /**
         * Method to hookup the initial "handshake" between the two parties (window and iframe) So they can start their communication
         * @param {Number} retry - retry in milliseconds
         * @returns {Boolean} indication if handshake initiated
         * @private
         */
        function _handshake(retry) {
            // Remove load handler if needed
            _removeTimer.call(this, true);

            if (!this.disposed && !this.ready) {
                if (!_isNativeMessageChannelSupported.call(this)) {
                    this.targetOrigin = this.targetOrigin || PostMessageUtilities.resolveOrigin(this.target) || "*";
                }

                if (!this.hosted) {
                    if (_isNativeMessageChannelSupported.call(this)) {
                        try {
                            if (this.neutered) {
                                this.channelFactory();
                            }
                            this.target.contentWindow.postMessage(this.token, this.targetOrigin, [ this.dispatcher ]);
                            this.neutered = true;
                        }
                        catch(ex) {
                            /* istanbul ignore next  */
                            return false;
                        }
                    }
                    else {
                        this.target.contentWindow.postMessage(this.token, this.targetOrigin);
                    }
                }
            }

            if (!this.disposed && !this.ready && retry) {
                if (0 < this.handshakeAttempts) {
                    this.handshakeAttempts--;
                    this.rmtimer = PostMessageUtilities.delay(_handshake.bind(this, retry), retry);
                }
                else {
                    this.onready(new Error("Loading: Operation Timeout!"));
                }
            }

            return true;
        }

        /**
         * Method to mark ready, and process queued/waiting messages if any
         * @private
         */
        function _onReady() {
            if (!this.disposed && !this.ready) {
                this.ready = true;

                // Handshake was successful, Channel is ready for messages
                // Set the counter back to original value for dealing with iframe reloads
                this.handshakeAttempts = this.handshakeAttemptsOrig;

                // Process queued messages if any
                if (this.messageQueue && this.messageQueue.length) {
                    PostMessageUtilities.delay(function() {
                        var message;
                        var parsed;

                        if (!this.disposed && this.ready) {
                            while (this.messageQueue && this.messageQueue.length) {
                                message = this.messageQueue.shift();
                                try {
                                    parsed = _prepareMessage.call(this, message);
                                    this.receiver.postMessage(parsed);
                                }
                                catch(ex) {
                                    /* istanbul ignore next  */
                                    PostMessageUtilities.log("Error while trying to post the message from queue", "ERROR", "PostMessageChannel");
                                }
                            }

                            // Invoke the callback for ready
                            this.onready();
                        }
                    }.bind(this));
                }
                else {
                    // Invoke the callback for ready
                    this.onready();
                }
            }
        }

        /**
         * Method to enable running a callback once the document body is ready
         * @param {Object} [options] Configuration options
         * @param {Function} options.onready - the callback to run when ready
         * @param {Object} [options.doc = root.document] - document to refer to
         * @param {Number} [options.delay = 0] - milliseconds to delay the execution
         * @private
         */
        function _waitForBody(options) {
            options = options || {};
            var onready = options.onready;
            var doc = options.doc || root.document;
            var delay = options.delay;

            function _ready() {
                if (doc.body) {
                    onready();
                }
                else {
                    PostMessageUtilities.delay(_ready, delay || DEFAULT_BODY_LOAD_DELAY);
                }
            }

            PostMessageUtilities.delay(_ready, delay || false);
        }

        /**
         * Creates an iFrame in memory and sets the default attributes except the actual URL
         * Does not attach to DOM at this point
         * @param {Object} options a passed in configuration options
         * @param {String} options.url - the url to load,
         * @param {String} [options.style] - the CSS style to apply
         * @param {String} [options.style.width] width of iframe
         * @param {String} [options.style.height] height of iframe
         *          .....
         * @param {Boolean} [options.bust = true] - optional flag to indicate usage of cache buster when loading the iframe (default to true),
         * @param {Function} [options.callback] - a callback to invoke after the iframe had been loaded,
         * @param {Object} [options.context] - optional context for the callback
         * @param {Object} [container] - the container in which the iframe should be created (if not supplied, document.body will be used)
         * @returns {Element} the attached iFrame element
         * @private
         */
        function _createIFrame(options, container) {
            var frame = document.createElement("IFRAME");
            var name = PostMessageUtilities.createUniqueSequence(IFRAME_PREFIX + PostMessageUtilities.SEQUENCE_FORMAT);
            var delay = options.delayLoad;
            var defaultAttributes = {
                "id": name,
                "name" :name,
                "tabindex": "-1",       // To prevent it getting focus when tabbing through the page
                "aria-hidden": "true",  // To prevent it being picked up by screen-readers
                "title":  "",           // Adding an empty title for accessibility
                "role": "presentation", // Adding a presentation role http://yahoodevelopers.tumblr.com/post/59489724815/easy-fixes-to-common-accessibility-problems
                "allowTransparency":"true"
            };
            var defaultStyle = {
                width :"0px",
                height : "0px",
                position :"absolute",
                top : "-1000px",
                left : "-1000px"
            };

            options.attributes = options.attributes || defaultAttributes;
            for (var key in options.attributes){
                if (options.attributes.hasOwnProperty(key)) {
                    frame.setAttribute(key, options.attributes[key]);
                }
            }

            options.style = options.style || defaultStyle;
            if (options.style) {
                for (var attr in options.style) {
                    if (options.style.hasOwnProperty(attr)) {
                        frame.style[attr] = options.style[attr];
                    }
                }
            }

            // Append and hookup after body tag opens
            _waitForBody({
                delay: delay,
                onready: function() {
                    (container || document.body).appendChild(frame);
                    this.rmload = _addLoadHandler.call(this, frame);
                    _setIFrameLocation.call(this, frame, options.url, (false !== options.bust));
                }.bind(this)
            });

            return frame;
        }

        /**
         * Add load handler for the iframe to make sure it is loaded
         * @param {Object} frame - the actual DOM iframe
         * @returns {Function} the remove handler function
         * @private
         */
        function _addLoadHandler(frame) {
            var load = function() {
                this.loading = false;

                if (this.handshakeAttempts === this.handshakeAttemptsOrig) {
                    // Probably a first try for handshake or a reload of the iframe,
                    // Either way, we'll need to perform handshake, so ready flag should be set to false (if not already)
                    this.ready = false;
                }

                _handshake.call(this, this.handshakeInterval);
            }.bind(this);

            PostMessageUtilities.addEventListener(frame, "load", load);

            return function() {
                _removeLoadHandler(frame, load);
            };
        }

        /**
         * Remove load handler for the iframe
         * @param {Object} frame - the actual DOM iframe
         * @param {Function} handler - the actual registered load handler
         * @private
         */
        function _removeLoadHandler(frame, handler) {
            PostMessageUtilities.removeEventListener(frame, "load", handler);
        }

        /**
         * Sets the iFrame location using a cache bust mechanism,
         * making sure the iFrame is actually loaded and not from cache
         * @param {Object} frame - the iframe DOM object
         * @param {String} src - the source url for the iframe
         * @param {Boolean} bust - flag to indicate usage of cache buster when loading the iframe
         * @private
         */
        function _setIFrameLocation(frame, src, bust){
            src += (0 < src.indexOf("?") ? "&" : "?");

            if (bust) {
                src += "bust=";
                src += (new Date()).getTime() + "&";
            }

            src += ((this.hostParam ? "hostParam=" + this.hostParam + "&" + this.hostParam + "=" : "lpHost=") + encodeURIComponent(PostMessageUtilities.getHost(void 0, frame, true)));

            if (!_isNativeMessageChannelSupported.call(this)) {
                src += "&lpPMCPolyfill=true";
            }

            if (false === this.useObjects) {
                src += "&lpPMDeSerialize=true";
            }

            frame.setAttribute("src", src);
        }

        return {
            initialize: initialize,
            postMessage: postMessage,
            dispose: dispose
        };
    }());

    // attach properties to the exports object to define
    // the exported module properties.
    if (!hide) {
        exports.PostMessageChannel = PostMessageChannel;
    }
    return PostMessageChannel;
}));