Source: Tracker/utils.js

/**
 * @class
 * @classdesc Utility methods.
 * @name Utils
 * @public
 */
var Utils = function () {

    var self = this;

    var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;
    var whitespace = "[\\x20\\t\\r\\n\\f]";
    var rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g");

    /**
     * Serialize any element.
     * @memberof Utils#
     * @param obj {Object} Object to serialize
     * @return {String}
     * @private
     */
    function serialJSON(obj) {
        var t = typeof obj;
        if (t !== 'object' || obj === null) {
            if (t === 'string') {
                obj = '"' + obj + '"';
            }
            return String(obj);
        } else {
            var n, v, json = [],
                arr = (obj.constructor === Array);
            for (n in obj) {
                if (obj.hasOwnProperty(n)) {
                    v = obj[n];
                    t = typeof (v);
                    if (t !== 'function' && t !== 'undefined') {
                        if (t === 'string') {
                            v = '"' + v.replace(/[^\\]"/g, '\\"') + '"';
                        } else if (t === 'object' && v !== null) {
                            v = serialJSON(v);
                        }
                        json.push((arr ? '' : '"' + n + '":') + String(v));
                    }
                }
            }
            return (arr ? '[' : '{') + String(json) + (arr ? ']' : '}');
        }
    }

    /**
     * Trim JSON string.
     * @memberof Utils#
     * @param text {String} Text to trim
     * @return {String}
     * @private
     */
    function trimJSONString(text) {
        return text === null ?
            '' :
            (text + '').replace(rtrim, '');
    }

    /**
     * Parse any string.
     * @memberof Utils#
     * @param data {string} String to parse
     * @return {*}
     * @private
     */
    function parseJSON(data) {
        var requireNonComma,
            depth = null,
            str = trimJSONString(data + '');
        // Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
        // after removing valid tokens
        return str && !trimJSONString(str.replace(rvalidtokens, function (token, comma, open, close) {
            // Force termination if we see a misplaced comma
            if (requireNonComma && comma) {
                depth = 0;
            }
            // Perform no more replacements after returning to outermost depth
            if (depth === 0) {
                return token;
            }
            // Commas must not follow "[", "{", or ","
            requireNonComma = open || comma;
            // Determine new depth
            // array/object open ("[" or "{"): depth += true - false (increment)
            // array/object close ("]" or "}"): depth += false - true (decrement)
            // other cases ("," or primitive): depth += true - true (numeric cast)
            depth += !close - !open;
            // Remove this token
            return '';
        })) ?
            (Function('return ' + str))() :
            null;
    }

    /**
     * Check if local storage is both supported and available
     * @name isLocalStorageAvailable
     * @memberof Utils#
     * @return {boolean}
     * @public
     */
    self.isLocalStorageAvailable = function () {
        try {
            var storage = localStorage,
                x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        } catch (e) {
            return false;
        }
    };

    /**
     * Check if beacon method is supported and available
     * @name isBeaconMethodAvailable
     * @memberof Utils#
     * @return {boolean}
     * @public
     */
    self.isBeaconMethodAvailable = function () {
        return (window.navigator && typeof window.navigator.sendBeacon === "function");
    };

    /**
     * Get a stored value from name
     * @name getStorageData
     * @memberof Utils#
     * @function
     * @param name {String} Name of the stored data
     * @return {*} Return null if the stored data does not exist
     * @private
     */
    function getStorageData(name) {
        var storedData = null;
        if (self.isLocalStorageAvailable()) {
            storedData = localStorage.getItem(name)
        }
        if (storedData === null) {
            var cookies = document.cookie;
            var regExp = new RegExp('(?:^| )' + name + '=([^;]+)');
            var result = regExp.exec(cookies);
            if (result !== null) {
                storedData = result[1];
            }
        }
        if (storedData !== null) {
            try {
                storedData = decodeURIComponent(storedData);
            } catch (e) {
            }
        }
        return storedData;
    }

    /**
     * Base64 encode / decode
     * *http://www.webtoolkit.info/
     * @memberof Utils#
     * @Object
     * @public
     */
    self.Base64 = {

        // private property
        _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

        // public method for encoding
        encode: function (input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;

            input = self.Base64._utf8_encode(input);

            while (i < input.length) {

                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);

                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;

                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                }

                output = output +
                    this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                    this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

            }

            return output;
        },

        // public method for decoding
        decode: function (input) {
            var output = "";
            var chr1, chr2, chr3;
            var enc1, enc2, enc3, enc4;
            var i = 0;

            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

            while (i < input.length) {

                enc1 = this._keyStr.indexOf(input.charAt(i++));
                enc2 = this._keyStr.indexOf(input.charAt(i++));
                enc3 = this._keyStr.indexOf(input.charAt(i++));
                enc4 = this._keyStr.indexOf(input.charAt(i++));

                chr1 = (enc1 << 2) | (enc2 >> 4);
                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                chr3 = ((enc3 & 3) << 6) | enc4;

                output = output + String.fromCharCode(chr1);

                if (enc3 != 64) {
                    output = output + String.fromCharCode(chr2);
                }
                if (enc4 != 64) {
                    output = output + String.fromCharCode(chr3);
                }

            }

            output = self.Base64._utf8_decode(output);

            return output;

        },

        // private method for UTF-8 encoding
        _utf8_encode: function (string) {
            string = string.replace(/\r\n/g, "\n");
            var utftext = "";

            for (var n = 0; n < string.length; n++) {

                var c = string.charCodeAt(n);

                if (c < 128) {
                    utftext += String.fromCharCode(c);
                } else if ((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                } else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                }

            }

            return utftext;
        },

        // private method for UTF-8 decoding
        _utf8_decode: function (utftext) {
            var string = "";
            var i = 0;
            var c, c2, c3;
            c = c2 = c3 = 0;

            while (i < utftext.length) {

                c = utftext.charCodeAt(i);

                if (c < 128) {
                    string += String.fromCharCode(c);
                    i++;
                } else if ((c > 191) && (c < 224)) {
                    c2 = utftext.charCodeAt(i + 1);
                    string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                    i += 2;
                } else {
                    c2 = utftext.charCodeAt(i + 1);
                    c3 = utftext.charCodeAt(i + 2);
                    string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                    i += 3;
                }

            }

            return string;
        }

    };

    /**
     * Load a script.
     * @name loadScript
     * @memberof Utils#
     * @function
     * @param scriptObj {object} Object containing script properties (url)
     * @param callback {function} Function to call on success or on error
     * @public
     */
    self.loadScript = function (scriptObj, callback) {
        var newScript;
        callback = callback || function () {
        };

        var errorCallback = function (event) {
            newScript.onload = newScript.onreadystatechange = newScript.onerror = null;
            callback({msg: 'script not loaded', event: event}); // first param not null means an error has occured
        };
        var onloadCallback = function (event) {
            event = event || window.event;
            // Check for different events in IEs
            if (event.type === "load" || (/loaded|complete/.test(newScript.readyState) && (!document.documentMode || document.documentMode < 9))) {
                newScript.onload = newScript.onreadystatechange = newScript.onerror = null;
                callback(null, event);
            }
        };
        newScript = document.createElement("script");
        newScript.type = "text/javascript";
        newScript.src = scriptObj.url;
        newScript.async = false;
        newScript.defer = false;
        newScript.onload = newScript.onreadystatechange = onloadCallback;
        newScript.onerror = errorCallback;
        var head = document.head || document.getElementsByTagName("head")[0];
        head.insertBefore(newScript, head.lastChild);
    };

    // We deliberately ignore the second parameter reserved for us :)
    /**
     * Copy an object which doesnt' contain functions/objects.
     * @name cloneSimpleObject
     * @memberof Utils#
     * @inner
     * @function
     * @param inst {Object} Item that you want to clone
     * @param delUndefined {boolean} If true, undefined properties are not cloned
     * @return {Object} clone of the instance
     * @public
     */
    self.cloneSimpleObject = function (inst, delUndefined) {
        /*Si l'instance source n'est pas un objet ou qu'elle ne vaut rien c'est une feuille donc on la retourne*/
        if (typeof (inst) !== 'object' || inst === null || inst instanceof Date) {
            return inst;
        }
        /*On appelle le constructeur de l'instance source pour créer une nouvelle instance de la même classe*/
        var newInst = new inst.constructor;
        /*On parcourt les propriétés de l'objet et on les recopie dans la nouvelle instance*/
        for (var i in inst) {
            if (inst.hasOwnProperty(i)) {
                //On gère le cas où il a été demandé (delUndefined) de ne pas copier les propriétés undefined
                if (i !== undefined && (!delUndefined || inst[i] !== undefined)) {
                    newInst[i] = self.cloneSimpleObject(inst[i]);
                }
            }
        }
        /*On retourne la nouvelle instance*/
        return newInst;
    };

    /**
     * Check if object is empty.
     * @name isEmptyObject
     * @memberof Utils#
     * @function
     * @param tagObject {Object}
     * @return {boolean}
     * @public
     */
    self.isEmptyObject = function (tagObject) {
        for (var p in tagObject) {
            if (tagObject.hasOwnProperty(p)) {
                return false;
            }
        }
        return true;
    };

    /**
     * Check if parameter is an object.
     * @name isObject
     * @memberof Utils#
     * @function
     * @param contextualObject {*}
     * @return {boolean}
     * @public
     */
    self.isObject = function (contextualObject) {
        return ((contextualObject !== null) && (typeof contextualObject === 'object') && !(contextualObject instanceof Array));
    };

    // Constants to manage flattening
    self.ATVALUE = '_ATVALUE';
    self.ATPREFIX = '_ATPREFIX';

    /**
     * Adding flatten property with value from source object into destination.
     * @memberof Utils#
     * @function
     * @param source {Object} Source object
     * @param parentPath {string|null} Path from parent
     * @param destination {Object} Final object
     * @param parentPrefix {string|null} Prefix from parent
     * @public
     */
    self.object2Flatten = function (source, parentPath, destination, parentPrefix) {
        var splittedObject = {};
        var prefix = '';
        var path = '';
        var levels = [];
        var newPath = '';
        var i = 0;
        for (var sourceKey in source) {
            if (source.hasOwnProperty(sourceKey)) {
                splittedObject = self.splitProtocolAndKey(sourceKey);
                prefix = splittedObject.prefix || parentPrefix || '';
                path = (parentPath ? parentPath + '_' : '') + splittedObject.key;
                if (self.isObject(source[sourceKey])) {
                    self.object2Flatten(source[sourceKey], path, destination, prefix);
                } else {
                    levels = path.split('_');
                    newPath = '';
                    for (i = 0; i < levels.length; i++) {
                        splittedObject = self.splitProtocolAndKey(levels[i]);
                        prefix = splittedObject.prefix || prefix;
                        newPath += splittedObject.key + ((i < levels.length - 1) ? '_' : '');
                    }
                    path = newPath || path;
                    destination[path] = destination[path] || {};
                    destination[path][self.ATVALUE] = source[sourceKey];
                    destination[path][self.ATPREFIX] = prefix;
                }
            }
        }
    };

    /**
     * Create object from flatten property.
     * @memberof Utils#
     * @function
     * @param destination {Object} Final object
     * @param sourceKey {string} Key to process
     * @param sourceValue {Object} Value to set
     * @public
     */
    self.flatten2Object = function (destination, sourceKey, sourceValue) {

        var levels = sourceKey.split('_');
        var parentObject = destination;
        var currentLevel;

        // Scrolling through data object
        var i;
        for (i = 0; i < levels.length - 1; i++) {
            currentLevel = levels[i];
            if (!parentObject[currentLevel]) {
                parentObject[currentLevel] = {};
            }
            parentObject = parentObject[currentLevel];
        }

        // Update parent object if necessary
        if (parentObject.hasOwnProperty(self.ATVALUE)) {
            var parentValue = parentObject[self.ATVALUE];
            var parentPrefix = parentObject[self.ATPREFIX];
            delete parentObject[self.ATVALUE];
            delete parentObject[self.ATPREFIX];
            parentObject['$'] = {};
            parentObject['$'][self.ATVALUE] = parentValue;
            parentObject['$'][self.ATPREFIX] = parentPrefix;
        }

        // Adding current value
        var sourceCopyValue = self.cloneSimpleObject(sourceValue);
        if (parentObject[levels[i]]) {
            parentObject[levels[i]]['$'] = sourceCopyValue;
        } else {
            parentObject[levels[i]] = sourceCopyValue;
        }

    };

    /**
     * Get formatted content object from flatten source.
     * @memberof ATInternet.Tracker.Plugins.AvInsights#
     * @function
     * @param source {Object} Source object
     * @public
     */
    self.getFormattedObject = function (source) {
        var content = {};
        var newKey;
        for (var key in source) {
            if (source.hasOwnProperty(key)) {
                if (!source[key].hasOwnProperty(self.ATVALUE)) {
                    content[key] = self.getFormattedObject(source[key]);
                } else {
                    newKey = source[key][self.ATPREFIX] ? source[key][self.ATPREFIX] + ':' + key : key;
                    content[newKey] = source[key][self.ATVALUE];
                }
            }
        }
        return content;
    };

    /**
     * Complete the first level of an object with another.
     * @name completeFstLevelObj
     * @memberof Utils#
     * @function
     * @param target {object}
     * @param source {object}
     * @param overload {boolean} If true, properties of the target will be overloaded by source ones if they exist
     * @return {object}
     * @public
     */
    self.completeFstLevelObj = function (target, source, overload) {
        if (target) {
            if (source) {
                for (var key in source) {
                    if (source.hasOwnProperty(key)) {
                        if (!target[key] || overload) {
                            target[key] = source[key];
                        }
                    }
                }
            }
        } else {
            target = source;
        }
        return target;
    };

    /**
     * Get all keys from object.
     * @name getObjectKeys
     * @memberof Utils#
     * @function
     * @param object {object}
     * @return {Array}
     * @public
     */
    self.getObjectKeys = function (object) {
        var keys = [];
        for (var elem in object) {
            if (object.hasOwnProperty(elem)) {
                keys.push(elem);
            }
        }
        return keys;
    };

    /**
     * Change the keys of object to lowercase
     * @name objectToLowercase
     * @memberof Utils#
     * @function
     * @param tagObject {Object} Object from tag
     * @return {Object}
     * @public
     */
    self.objectToLowercase = function (tagObject) {
        var objUtil = {};
        for (var key in tagObject) {
            if (tagObject.hasOwnProperty(key)) {
                if (self.isObject(tagObject[key])) {
                    objUtil[key.toLowerCase()] = self.objectToLowercase(tagObject[key]);
                } else {
                    objUtil[key.toLowerCase()] = tagObject[key];
                }
            }
        }
        return objUtil;
    };

    /**
     * Process event key.
     * @name splitProtocolAndKey
     * @memberof Utils#
     * @function
     * @param key {String} Event key to split
     * @return {Object}
     * @public
     */
    self.splitProtocolAndKey = function (key) {
        if ((key.length < 2) || (key[1] !== ':')) {
            return {
                'prefix': '',
                'key': key.toLowerCase()
            };
        }
        if ((key.length < 4) || (key[3] !== ':')) {
            return {
                'prefix': key.substring(0, 1).toLowerCase(),
                'key': key.substring(2, key.length).toLowerCase()
            };
        }
        return {
            'prefix': key.substring(0, 3).toLowerCase(),
            'key': key.substring(4, key.length).toLowerCase()
        };
    };

    /**
     * Serialize any element. Use JSON.stringify if possible.
     * @name jsonSerialize
     * @memberof Utils#
     * @function
     * @param obj {Object} Javascript object that you want to serialize to JSON
     * @return {Object|String} Serialized JSON
     * @public
     */
    self.jsonSerialize = function (obj) {
        try {
            if (typeof JSON !== 'undefined' && JSON.stringify) {
                return JSON.stringify(obj);
            } else {
                return serialJSON(obj);
            }
        } catch (e) {
            return null;
        }
    };

    /**
     * Parse a string. Use JSON.parse if possible.
     * @name jsonParse
     * @memberof Utils#
     * @function
     * @param str {String} JSON string that you want to parse to Javascript object
     * @return {Object}
     * @public
     */
    self.jsonParse = function (str) {
        try {
            if (typeof JSON !== 'undefined' && JSON.parse) {
                return JSON.parse(str + '');
            } else {
                return parseJSON(str);
            }
        } catch (e) {
            return null;
        }
    };

    /**
     * Trim a string. Use String.prototype.trim if possible.
     * @name trim
     * @memberof Utils#
     * @function
     * @param str {String} string to trim
     * @return {String}
     * @public
     */
    self.trim = function (str) {
        try {
            if (!String.prototype.trim) {
                // Polyfill
                return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
            }
            return str.trim();
        } catch (e) {
            return str;
        }
    };

    /**
     * Search an element in an array. Use Array.indexOf if possible.
     * @name arrayIndexOf
     * @memberof Utils#
     * @function
     * @param arr {Array}
     * @param elemToSearch {*}
     * @return {number}
     * @public
     */
    self.arrayIndexOf = function (arr, elemToSearch) {
        if (Array.prototype.indexOf) {
            var index = -1;
            if (typeof arr.indexOf(elemToSearch) !== 'undefined') {
                index = arr.indexOf(elemToSearch)
            }
            return index;
        } else {
            return (function (searchElement) {
                "use strict";
                if (this == null) {
                    throw new TypeError();
                }
                var t = Object(this);
                var len = t.length >>> 0;
                if (len === 0) {
                    return -1;
                }
                var n = 0;
                if (arguments.length > 1) {
                    n = Number(arguments[1]);
                    if (n != n) { // shortcut for verifying if it's NaN
                        n = 0;
                    } else if (n != 0 && n != Infinity && n != -Infinity) {
                        n = (n > 0 || -1) * Math.floor(Math.abs(n));
                    }
                }
                if (n >= len) {
                    return -1;
                }
                var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
                for (; k < len; k++) {
                    if (k in t && t[k] === searchElement) {
                        return k;
                    }
                }
                return -1;
            }).apply(arr, [elemToSearch]);
        }
    };

    /**
     * Generate UUID.
     * @name uuid
     * @memberof Utils#
     * @function
     * @return {function}
     * @public
     */
    self.uuid = function () {

        /**
         * Generate GUID with alphanumeric characters (v4 format).
         * @name _v4
         * @memberof Utils#
         * @function
         * @return {string}
         * @public
         */
        function _v4() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }

        /**
         * Generate GUID with numeric characters.
         * @name _num
         * @memberof Utils#
         * @function
         * @param len {number} Length of the generated GUID
         * @return {string}
         * @public
         */
        function _num(len) {
            var d = new Date();
            var format = function (a) {
                a = a - Math.floor(a / 100) * 100;
                if (a < 10) {
                    return '0' + a;
                } else {
                    return String(a);
                }
            };
            var rand = function (n) {
                return Math.floor((Math.random() * 9 + 1) * Math.pow(10, n - 1));
            };
            return format(d.getHours()) + '' + format(d.getMinutes()) + '' + format(d.getSeconds()) + '' + rand(len - 6);
        }

        return {
            v4: _v4,
            num: _num
        };
    };

    /**
     * Check if we are in Safari previewing case.
     * @name isPreview
     * @memberof Utils#
     * @function
     * @return {boolean}
     * @public
     */
    self.isPreview = function () {
        return (window.navigator && window.navigator.loadPurpose === 'preview');
    };

    /**
     * Check if we are in Chrome or IE prerendering case.
     * @name isPrerender
     * @memberof Utils#
     * @function
     * @param callback {function}
     * @return {boolean}
     * @public
     */
    self.isPrerender = function (callback) {
        var visibilityChangeEvent;
        var isPrerender = false;
        //Prefixes: Chrome, IE
        var prefixes = ['webkit', 'ms'];
        if (document.visibilityState === 'prerender') {
            //Opera 12.10 and Firefox 18 and later support
            visibilityChangeEvent = 'visibilitychange';
        } else {
            for (var i = 0; i < prefixes.length; i++) {
                if (document[prefixes[i] + 'VisibilityState'] === 'prerender') {
                    visibilityChangeEvent = prefixes[i] + 'visibilitychange';
                }
            }
        }
        if (typeof visibilityChangeEvent !== 'undefined') {
            var _manageCallback = function (event) {
                callback(event);
                self.removeEvtListener(document, visibilityChangeEvent, _manageCallback);
            };
            self.addEvtListener(document, visibilityChangeEvent, _manageCallback);
            isPrerender = true;
        }
        return isPrerender;
    };

    /**
     * Add an event listener to an object.
     * @name addEvtListener
     * @memberof Utils#
     * @function
     * @param obj {Object} DOM Element on which you want to add the event listener
     * @param event {string} Event you need to listen
     * @param callback {function} When event is triggered, it takes one parameter which is the event object
     * @public
     */
    self.addEvtListener = function (obj, event, callback) {
        if (obj.addEventListener) {
            obj.addEventListener(event, callback, false);
        } else if (obj.attachEvent) {
            obj.attachEvent('on' + event, callback);
        }
    };

    /**
     * Remove an event listener from an object.
     * @name removeEvtListener
     * @memberof Utils#
     * @function
     * @param obj {Object} DOM Element on which you want to add the event listener
     * @param event {string} Event you need to listen
     * @param callback {function}
     * @public
     */
    self.removeEvtListener = function (obj, event, callback) {
        if (obj.removeEventListener) {
            obj.removeEventListener(event, callback, false);
        } else if (obj.detachEvent) {
            obj.detachEvent('on' + event, callback);
        }
    };

    /**
     * Make a unique number with a given string.
     * @name hashcode
     * @memberof Utils#
     * @function
     * @param str
     * @return {number}
     * @public
     */
    self.hashcode = function (str) {
        var hash = 0;
        if (str.length === 0) return hash;
        for (var i = 0; i < str.length; i++) {
            var character = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + character;
            hash |= 0;
        }
        return hash;
    };

    /**
     * Force document location.
     * @name setLocation
     * @memberof Utils#
     * @function
     * @param contextObj {Object} Redirection's url and target (properties location & target)
     * @public
     */
    self.setLocation = function (contextObj) {
        var loc = contextObj['location'];
        var obj = window[contextObj['target']] || window;
        if (loc) {
            obj.location.href = loc;
        }
    };

    /****************************** Callback management *************************/

    /**
     * Dispatch event for callbacks
     * @name dispatchCallbackEvent
     * @memberof Utils#
     * @function
     * @param name {string} Callback's name
     * @public
     */
    self.dispatchCallbackEvent = function (name) {
        // Create the event.
        var event;
        if (typeof window.Event === 'function') {
            event = new Event('ATCallbackEvent');
        } else {
            try {
                event = document.createEvent('Event');
                // Define that the event name is 'ATCallbackEvent'.
                // Deprecated.
                event.initEvent && event.initEvent('ATCallbackEvent', true, true);
            } catch (e) {
            }
        }
        if (event && (typeof document.dispatchEvent === 'function')) {
            event.name = name;
            document.dispatchEvent(event);
        }
    };

    /**
     * Add callback event
     * @name addCallbackEvent
     * @memberof Utils#
     * @function
     * @param func {function} function to execute
     * @public
     */
    self.addCallbackEvent = function (func) {
        // Listen to the event.
        self.addEvtListener(document, 'ATCallbackEvent', func);
    };

    /**
     * Remove callback event
     * @name removeCallbackEvent
     * @memberof Utils#
     * @param func {function} function to remove
     * @function
     * @public
     */
    self.removeCallbackEvent = function (func) {
        self.removeEvent('ATCallbackEvent', func);
    };

    /****************************** Custom Event management *************************/

    /**
     * Polyfill the CustomEvent() constructor functionality in Internet Explorer 9 and higher
     * @memberof Utils#
     * @function
     * @private
     */
    (function () {
        if (typeof window.CustomEvent === 'function') {
            window.ATCustomEvent = window.CustomEvent;
            return;
        }

        function ATCustomEvent(evt, params) {
            params = params || {bubbles: false, cancelable: false, detail: undefined};
            var event;
            try {
                event = document.createEvent('CustomEvent');
                event.initCustomEvent(evt, params.bubbles, params.cancelable, params.detail);
            } catch (e) {
            }
            return event;
        }

        if (typeof window.Event === "function") {
            ATCustomEvent.prototype = window.Event.prototype;
        }
        window.ATCustomEvent = ATCustomEvent;
    })();

    /**
     * Add custom event
     * @name addEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param detailName {string} detail name
     * @param detailID {number} detail uuid
     * @param func {function} function to execute
     * @function
     * @public
     */
    self.addEvent = function (eventName, detailName, detailID, func) {
        self[eventName] = new ATCustomEvent(eventName, {
            detail: {
                name: detailName,
                id: detailID
            }
        });
        // Listen to the event.
        self.addEvtListener(document, eventName, func);
    };

    /**
     * Remove custom event
     * @name removeEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param func {function} function to execute
     * @function
     * @public
     */
    self.removeEvent = function (eventName, func) {
        // Remove the event listener.
        self.removeEvtListener(document, eventName, func);
    };

    /**
     * Dispatch custom event
     * @name dispatchEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param detailName {string} detail name
     * @function
     * @public
     */
    self.dispatchEvent = function (eventName, detailName) {
        // Init the event.
        self[eventName] = self[eventName] || new ATCustomEvent(eventName, {
            detail: {
                name: detailName,
                id: -1
            }
        });
        try {
            document.dispatchEvent(self[eventName]);
        } catch (e) {
        }
    };

    /****************************** Privacy management *************************/

    /**
     * Object used to manage authority
     * @name Privacy
     * @memberof Utils#
     * @inner
     * @constructor
     * @property {string} CONSENTNO
     * @private
     */
    var Privacy = function () {
        /* -------- Privacy properties -------- */
        var _thisPrivacy = this;
        _thisPrivacy.CONSENTNO = 'Consent-NO';
    };

    /**
     * Privacy object
     * @name privacy
     * @memberof Utils#
     * @public
     */
    self.privacy = new Privacy();

    /****************************** OPT-OUT management *************************/

    /**
     * OPT-OUT value
     * @name optedOut
     * @memberof Utils#
     * @private
     */
    self.optedOut = null;

    /**
     * Test value from OPT-OUT stored object
     * @memberof Utils#
     * @function
     * @return {Boolean}
     * @private
     */
    function isOptoutInStorage() {
        var sItem = getStorageData('atuserid');
        if (sItem) {
            var item = self.jsonParse(sItem) || self.jsonParse(self.Base64.decode(sItem));
            if (item !== null) {
                return item.val === 'OPT-OUT';
            }
        }
        return false;
    }

    /**
     * Add OPT-OUT event
     * @name addOptOutEvent
     * @memberof Utils#
     * @param eventID {number} uuid
     * @param func {function} function to associate
     * @function
     * @public
     */
    self.addOptOutEvent = function (eventID, func) {
        self.addEvent('ATOptOutEvent', 'clientsideuserid', eventID, func);
    };

    /**
     * Remove OPT-OUT event
     * @name removeOptOutEvent
     * @memberof Utils#
     * @param func {function} function to remove
     * @function
     * @public
     */
    self.removeOptOutEvent = function (func) {
        self.removeEvent('ATOptOutEvent', func);
    };

    /**
     * Dispatch event for OPT-OUT
     * @name dispatchOptOutEvent
     * @memberof Utils#
     * @param active {boolean} Activate or deactivate OPT-OUT
     * @function
     * @private
     */
    self.dispatchOptOutEvent = function (active) {
        self.optedOut = active;
        self.dispatchEvent('ATOptOutEvent', 'clientsideuserid');
    };

    /**
     * Activate OPT-OUT mode adding OPT-OUT value in hit and storage for idclient parameter
     * @name userOptedOut
     * @memberof Utils#
     * @function
     * @public
     */
    self.userOptedOut = function () {
        self.dispatchOptOutEvent(true);
    };

    /**
     * Deactivate OPT-OUT mode
     * @name userOptedIn
     * @memberof Utils#
     * @function
     * @public
     */
    self.userOptedIn = function () {
        self.dispatchOptOutEvent(false);
    };

    /**
     * Get OPTOUT activation status
     * @name isOptedOut
     * @memberof Utils#
     * @function
     * @public
     */
    self.isOptedOut = function () {
        if (self.optedOut === null) {
            self.optedOut = isOptoutInStorage();
        }
        return !!self.optedOut;
    };

    /****************************** Consent Page management *************************/

    /**
     * Specify whether consent has been obtained preventing or allowing data to be stored in cookies or local storage
     * @name consentReceived
     * @memberof Utils#
     * @param active {boolean} false to prevent data to be stored, true to allow data to be stored
     * @function
     * @public
     */
    self.consentReceived = function (active) {
        self.consent = !!active;
    };

    /**
     * Consent page option value
     * @name consent
     * @memberof Utils#
     * @private
     */
    self.consent = true;

    /****************************** Click event management *************************/

    /**
     * Check if a key allowing a tab opening is pressed during a click
     * @name isTabOpeningAction
     * @memberof Utils#
     * @param event {Object} event object
     * @function
     * @public
     * @return {boolean}
     */
    self.isTabOpeningAction = function (event) {
        var isTabOpening = false;
        if (event && (event.ctrlKey ||
            event.shiftKey ||
            event.metaKey ||
            (event.button && event.button === 1))
        ) {
            isTabOpening = true;
        }
        return isTabOpening;
    };

    // Constants for managing click actions according to the DOM Element
    self.CLICKS_REDIRECTION = 'redirection';
    self.CLICKS_FORM = 'form';
    self.CLICKS_MAILTO = 'mailto';

    /*******************************************************************************/

    // For unit tests on private elements !!!
    /* @if test */
    self.serialJSON = serialJSON;
    self.parseJSON = parseJSON;
    self.getStorageData = getStorageData;
    self.isOptoutInStorage = isOptoutInStorage;
    /* @endif */
};

/**
 * Module with utility methods.
 * @name ATInternet.Utils
 * @memberof ATInternet
 * @type {Utils}
 * @public
 * @see {@link Utils}
 */
ATInternet.Utils = new Utils();