UNPKG

safeframe

Version:

SafeFrame provides a consistent JS API to include 3rd party content

1,610 lines (1,403 loc) 75.8 kB
/* * Copyright (c) 2012, Interactive Advertising Bureau * All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @fileOverview This file contains the base library functionality need for both the publisher/host and vendor/client sides of the SafeFrames library. Contains JavaScript language extensions and base level dom reading / manipulation * @author <a href="mailto:ssnider@yahoo-inc.com">Sean Snider</a> * @version 1.1.0 */ /** * @namespace $sf Defines the base $sf namespace. This file should be the 1st file whenever including any SafeFrames js files * */ /* * Whenever you define a top level namespace, you need to put a "var" keyword statement in front of it. * This is b/c in Internet Explorer, elements with ID attributes can be treated as global variable. In turn * someone could have been dumb and have an element in the page named "$sf". */ if (global["$sf"]) { try { $sf.ver = "1-1-0"; $sf.specVersion = "1.1"; } catch (sf_lib_err) { } } else { var $sf = { ver: "1-1-0", specVersion: "1.1" }; }; /** * @namespace $sf.lib Defines the helper library functions and clases used througout the SafeFrames implementation * @name $sf.lib * */ /** * @namespace $sf.env Defines object / properties / functions that include information about the environment * @name $sf.env * */ /* * We always use a common pattern of enclosing our code within an anonymous function wrapper * such that we don't create any global variables (other than namespaces). * */ /** @ignore */ (function(win) { /* * Below we have some internal private variables. . * We always define variables at the top of any given function, using comma notation * if at all possible to reduce size. * * Often times here we have representations of values that are constants or constant strings. * * Note that we purposefully use escape / unescape functions rather than encodeURIComponent/decodeURIComponent * The reasons are that we want values that would not be ascii escaped by the newer function to get escaped, * and because escape / unescape are so legacy and ancient that they are actually very very fast. * */ var q = "?", a = "&", eq = "=", OBJ = "object", FUNC = "function", STR = "string", NUM = "number", RP = "replace", LEN = "length", DOC = "document", PROTO = "prototype", N = (win && win.Number), M = (win && win.Math), d = (win && win[DOC]), nav = (win && win.navigator), ua = (nav && nav.userAgent) || "", TLC = "toLowerCase", GT = "getAttribute", ST = "setAttribute", RM = "removeAttribute", GTE = "getElementsByTagName", DCLDED = "DOMContentLoaded", S = (win && win.String), back_slash = S.fromCharCode(92), two_slashes = back_slash+back_slash, dbl_quote = S.fromCharCode(34), esc_dbl_quote = back_slash+dbl_quote, plus_char = S.fromCharCode(43), scrip_str = 'scr'+dbl_quote+plus_char+dbl_quote+'ipt', BLANK_URL = "about:blank", NODE_TYPE = "nodeType", IFRAME = "iframe", GC = "CollectGarbage", ie_attach = "attachEvent", w3c_attach = "addEventListener", ie_detach = "detachEvent", w3c_detach = "removeEventListener", use_attach = "", use_detach = "", use_ie_old_attach = FALSE, IAB_LIB = "$sf.lib", IAB_ENV = "$sf.env", IAB_INF = "$sf.info", IE_GC_INTERVAL = 3000, TRUE = true, FALSE = false, NULL = null, EVT_CNCL_METHODS = { "preventDefault": 0, "stopImmediatePropagation": 0, "stopPropagation": 0, "preventBubble": 0 }, NUM_MAX = (N && N.MAX_VALUE), NUM_MIN = (-1 * NUM_MAX), _es = (win && win.escape), _ue = (win && win.unescape), isIE11 = !(global.ActiveXObject) && "ActiveXObject" in global, isIE = !isIE11 && (win && ("ActiveXObject" in win)), next_id = 0, useOldStyleAttrMethods = FALSE, gc_timer_id = 0, dom_is_ready = NULL, dom_last_known_tag_count = 0, dom_last_known_child_node = NULL, dom_ready_chk_max_tries = 300, dom_ready_chk_try_interval = 50, dom_ready_chk_tries = 0, dom_ready_chk_timer_id = 0, iframe_next_id = 0, iframe_cbs_attached = {}, evt_tgt_prop_a = "", evt_tgt_prop_b = "", iframe_msg_host_lib = NULL, cached_ua = NULL, /* private function variable references, we have lang set these private variables so that the dom lib below can access them quickly */ _cstr, _cnum, _callable, lang, dom, /* public functions that are dynamically defined based on whether IE is being used */ gc; /** * @namespace $sf.lib.lang Defines helper functions / objects for JavaScript common needs, such as type conversion and object serialization * @name $sf.lib.lang * */ (function() { var proto; /** * A function reference that does nothing. * * @memberOf $sf.lib.lang * @exports noop as $sf.lib.lang.noop * @static * @function * @public * @return undefined * */ function noop() {} /** * Forces type conversion of any JavaScript variable to a string value. * Note that "falsy" values or values that cannot be converted will be returned * as an empty string (""). * * @memberOf $sf.lib.lang * @exports cstr as $sf.lib.lang.cstr * @static * @public * @function * @param {*} str Any object that needs to be converted to a string value. * @return {String} The normalized string value. */ function cstr(str) { var typ = typeof str; if (typ == STR) return str; if (typ == NUM && !str) return "0"; if (typ == OBJ && str && str.join) return str.join(""); if (str === false) return 'false'; if (str === true) return 'true'; return (str) ? S(str) : ""; } /** * Forces type conversion of any JavaScript variable to a boolean. * "Falsy" values such as "", 0, null, and undefined all return false * String values of "0", "false", "no", "undefined", "null" also return false * * @memberOf $sf.lib.lang * @exports cbool as $sf.lib.lang.cbool * @static * @public * @function * @param {*} val Any JavaScript reference / value * @return {Boolean} The normalized boolean value * */ function cbool(val) { return (!val || val == "0" || val == "false" || val == "no" || val == "undefined" || val == "null") ? FALSE : TRUE; } /** * Forces type convertion of any JavaScript variable to a number. * Values / objects that cannot be converted, will be returned as NaN, unless * a default value is specified, in which case the default value is used. * * @memberOf $sf.lib.lang * @exports cnum as $sf.lib.lang.cnum * @static * @public * @function * @param {*} val Any JavaScript reference / value * @param {*} [defVal] use this value if original value cannot be converted to a number, or if value is less than min value, or if value is less than max value. * @param {Number} [minVal] specifies the lowest numerical value, if original value is less than this value, the defVal will be returned. * @param {Number} [maxVal] specifies the greatest numerical value, if original value is greater than this value, the defVal will be returned. * @return {Number|NaN|*} the converted value, otherwise NaN or default value * */ function cnum(val, defVal, minVal, maxVal) { var e; if (typeof val != NUM) { try { if (!val) { val = N.NaN; } else { val = parseFloat(val); } } catch (e) { val = N.NaN; } } if (maxVal == NULL) { maxVal = NUM_MAX; } if (minVal == NULL) { minVal = NUM_MIN; } return ((isNaN(val) || val < minVal || val > maxVal) && defVal != NULL) ? defVal : val; } /** * Checks that a function reference can be called safely. Sometimes function references are part * of objects that may have been garbage collected (such as a function reference from another window or dom element). * This method checks the reference by making sure it has a constructor and toString properties. * * Note that this doesn't mean that the function itself when called (or its subsquent call stack), can't throw an error. . . * simply that you are able to call it. . . * * @memberOf $sf.lib.lang * @exports callable as $sf.lib.lang.callable * @static * @public * @function * @param {Function} A reference to a JavaScript function * @return {Boolean} true if function can be called safely, otherwise false. * */ function callable(f) { var e; try { f = (f && typeof f == FUNC && f.toString() && (new f.constructor())) ? f : NULL; } catch (e) { f = NULL; } return !!(f); } /** * Generate a unique id string * * @memberOf $sf.lib.lang * @exports guid as $sf.lib.lang.guid * @static * @public * @function * @param {String} [prefix] a substring to use a prefix * @return {String} unique id string * */ function guid(prefix) { return cstr([prefix||"","_",time(),"_",rand(),"_",next_id++]); } /** * Mixed the properties of one object into another object. * Note that this function is recursive * * @memberOf $sf.lib.lang * @exports mix as $sf.lib.lang.mix * @static * @public * @function * @param {Object} r The object that will receive properties * @param {Object} s The object that will deliever properties * @param {Boolean} [owned] Whether or not to skip over properties that are part of the object prototype * @param {Boolean} [skipFuncs] Whether or not to skip over function references * @param {Boolean} [no_ovr] Whether or not to overwrite properties that may have already been filled out * @return {Object} The receiver object passed in with potentially new properties added * */ function mix(r, s, owned, skipFuncs,no_ovr) { var item, p,typ; if (!s || !r) return r; for (p in s) { item = s[p]; typ = typeof item; if (owned && !s.hasOwnProperty(p)) continue; if (no_ovr && (p in r)) continue; if (skipFuncs && typ == FUNC) continue; if (typ == OBJ && item) { if (item.slice) { item = mix([],item); } else { item = mix({},item); } } r[p] = item; } return r; } /** * Return the current time in milliseconds, from the epoch * * @memberOf $sf.lib.lang * @exports time as $sf.lib.lang.time * @public * @function * @static * @return {Number} current time * */ function time() { return (new Date()).getTime(); } /** * Return a random integer anywhere from 0 to 99 * * @memberOf $sf.lib.lang * @exports rand as $sf.lib.lang.rand * @public * @static * @function * @return {Number} random number * */ function rand() { return M.round(M.random()*100); } /** * Trim the begining and ending whitespace from a string. * Note that this function will convert an argument to a string first * for type safety purposes. If string cannot be converted, and empty string is returned * * @memberOf $sf.lib.lang * @exports trim as $sf.lib.lang.trim * @return {String} trimmed string * @public * @function * @static * */ function trim(str) { var ret = cstr(str); return (ret && ret[RP](/^\s\s*/, '')[RP](/\s\s*$/, '')); }; /** * Define a JavaScript Namespace within a given context * * @memberOf $sf.lib.lang * @exports def as $sf.lib.lang.def * @param {String} str_ns The name of the namespace in dot notation as a string (e.g. "Foo.bar") * @param {Object} [aug] defines the object at the end of the namespace. If namespace is already specified, and this object is provided, the namespace will be augmented with properties from this object. If nothing is passed in, defaults to using an empty object. * @param {Object} [root] the root object from which the namespace is defined. If not passed in defaults to the global/window object * @param {Boolean} [no_ovr] if true, properties already defined on root with the same name will be ignored * @public * @function * @static * @return {Object} The object at the end of the namespace * */ function def(str_ns, aug, root,no_ovr) { var obj = (root && typeof root == OBJ) ? root : win, idx = 0, per = ".", ret = NULL, ar, item; if (str_ns) { str_ns = cstr(str_ns); aug = (aug && typeof aug == OBJ) ? aug : NULL; if (str_ns.indexOf(per)) { ar = str_ns.split(per); while (item = ar[idx++]) { item = trim(item); if (idx == ar[LEN]) { if (obj[item] && aug) { ret = obj[item] = mix(obj[item],aug,FALSE,NULL,no_ovr); } else { if (no_ovr && (item in obj)) { ret = obj[item]; } else { ret = obj[item] = obj[item] || aug || {}; } } } else { if (no_ovr && (item in obj)) { ret = obj[item]; } else { ret = obj[item] = obj[item] || {}; } } obj = obj[item]; } } else { if (obj[str_ns] && aug) { ret = obj[str_ns] = mix(obj[str_ns], aug,FALSE,NULL,no_ovr); } else { ret = obj[str_ns] = obj[str_ns] || aug || {}; } } } return ret; } /** * Checks for the existence of a JavaScript namespace * as opposed to def, which will automatically define the namespace * with a given context. * * @memberOf $sf.lib.lang * @exports ns as $sf.lib.lang.ns * @param {String} str_ns A string with . or [] notation of a JavaScript namesace (e.g. "foo.bar.show", or "foo['bar']['show']"). * @param {Object} [root] the root object to check within. .defaults to global / window * @return {*} The endpoint reference of the namespace or false if not found * @public * @function * @static * */ function ns(str_ns, root) { var exp = /(\[(.{1,})\])|(\.\w+)/gm, exp2 = /\[(('|")?)((\s|.)*?)(('|")?)\]/gm, exp3 = /(\[.*)|(\..*)/g, exp4 = /\./gm, idx = 0, rootStr = "", exists = TRUE, obj, matches, prop; obj = root = root || win; if (str_ns) { str_ns = cstr(str_ns); if (str_ns) { str_ns = trim(str_ns); matches = str_ns.match(exp); if (matches) { rootStr = str_ns[RP](exp3, ""); matches.unshift(rootStr); while (prop = matches[idx++]) { prop = prop[RP](exp2, "$3")[RP](exp4, ""); if (!obj[prop]) { exists = FALSE; break; } obj = obj[prop]; } } else { prop = str_ns; obj = obj[prop]; } } else { exists = FALSE; } } else { exists = FALSE; } return (exists && obj) || FALSE; } /** * @function * Tests to see if the object passed in is an array * */ function isArray(obj){ if(obj == null){ return false; } if(typeof obj === "string"){ return false; } if(obj.length != null && obj.constructor == Array){ return true; } return false; } /** * Returns an escaped backslash for processing strings with HTML or JavaScript content * * @name $sf.lib.lang-_escaped_backslash * @function * @static * @private * */ function _escaped_backslash() { return two_slashes; } /** * Returns an escaped double-quote for processing strings with HTML or JavaScript content * * @name $sf.lib.lang-_escaped_dbl_quote * @function * @static * @private * */ function _escaped_dbl_quote() { return esc_dbl_quote; } /** * Returns an escaped return character for processing strings with HTML or JavaScript content * * @name $sf.lib.lang-_escaped_return * @function * @static * @private * */ function _escaped_return() { return "\\r"; } /** * Returns an escaped new line character for processing strings with HTML or JavaScript content * * @name $sf.lib.lang-_escaped_new_line * @function * @static * @private * */ function _escaped_new_line() { return "\\n"; } /** * Returns a seperated SCRIPT tag ("<script>" becomes "<scr"+"ipt>") for processing strings with HTML or JavaScript content * Assumes a regular expression of: /<(\/)*script([^>]*)>/gi * * @name $sf.lib.lang-_escaped_new_line * @function * @static * @private * */ function _safe_script_tag(main_match, back_slash, attrs) { return cstr(["<",back_slash,scrip_str,attrs,">"]); } /** * Given a string of HTML escape quote marks and seperate script tags so that browsers don't get tripped up * during processing. * * @memberOf $sf.lib.lang * @exports jssafe_html as $sf.lib.lang.jssafe_html * @param {String} str A string of HTML markup to be processed * @return {String} * @function * @static * @public * */ function jssafe_html(str) { var new_str = cstr(str); if (new_str) { new_str = new_str.replace(/(<noscript[^>]*>)(\s*?|.*?)(<\/noscript>)/gim, ""); new_str = new_str.replace(/\\/g, _escaped_backslash); new_str = new_str.replace(/\"/g, _escaped_dbl_quote); new_str = new_str.replace(/\n/g, _escaped_new_line); new_str = new_str.replace(/\r/g, _escaped_return); new_str = new_str.replace(/<(\/)*script([^>]*)>/gi, _safe_script_tag); new_str = new_str.replace(/\t/gi, ' ' ); new_str = cstr([dbl_quote,new_str,dbl_quote]); new_str = dbl_quote + new_str + dbl_quote; } return new_str; } /** * @class Intantiable class used to convert a delimited string into an object.<br /> * For example querystrings: "name_1=value_1&name_2=value_2" ==> {name_1:value_1,name_2:value_2};<br/><br /> * * Note that property values could also contain the same sPropDelim and sValueDelim strings. Proper string encoding should occur * to not trip up the parsing of the string. Said values may be ascii escaped, and in turn, along with the <i><b>bRecurse</b></i> constructor parameter set to true, will cause nested ParamHash objects to be created. * * @constructor * @memberOf $sf.lib.lang * @exports ParamHash as $sf.lib.lang.ParamHash * @param {String} [sString] The delimited string to be converted * @param {String} [sPropDelim="&"] The substring delimiter used to seperate properties. Default is "&". * @param {String} [sValueDelim="="] The substring delimited used to seperate values. Default is "=". * @param {Boolean} [bNoOverwrite=false] If true, when a name is encountered more than 1 time in the string it will be ignored. * @param {Boolean} [bRecurse=false] If true, when a value of a property that is parsed also has both the sPropDelim and sValueDelim inside, convert that value to another ParamHash object automatically * @example * var ph = new $sf.lib.lang.ParamHash("x=1&y=1&z=1"); * alert(ph.x); // == 1 * alert(ph.y); // == 1 * alert(ph.z); // == 1 * * @example * var ph = new $sf.lib.lang.ParamHash("x:1;y:2;z:3", ";", ":"); * alert(ph.x); // == 1 * alert(ph.y); // == 2 * alert(ph.z); // == 3 * * @example * var ph = new $sf.lib.lang.ParamHash("x=1&y=1&z=1&z=2"); * alert(ph.x); // == 1 * alert(ph.y); // 1 * alert(ph.z); //Note that z == 2 b/c of 2 properties with the same name * * @example * var ph = new $sf.lib.lang.ParamHash("x=1&y=1&z=1&z=2",null,null,true); //null for sPropDelim and sValueDelim == use default values of "&" and "=" respectively * alert(ph.x); // == 1 * alert(ph.y); // 1 * alert(ph.z); //Note that z == 1 b/c bNoOverwrite was set to true * * @example * //You can also do recursive processing if need be * var points = new $sf.lib.lang.ParamHash(), * point_1 = new $sf.lib.lang.ParamHash(), * point_2 = new $sf.lib.lang.ParamHash(); * * point_1.x = 100; * point_1.y = 75; * * point_2.x = 200; * point_2.y = 150; * * points.point_1 = point_1; * points.point_2 = point_2; * * var point_str = points.toString(); // == "point_1=x%3D100%26y%3D75%26&point_2=x%3D200%26y%3D150%26&"; * var points_copy = new $sf.lib.lang.ParamHash(point_str, null, null, true, true); //note passing true, b/c we want to recurse * * alert(points_copy.point_1.x) // == "100"; * * */ function ParamHash(sString, sPropDelim, sValueDelim, bNoOverwrite, bRecurse) { var idx, idx2, idx3, sTemp, sTemp2, sTemp3, me = this, pairs, nv, nm, added, cnt, io="indexOf",ss="substring", doAdd = FALSE, obj, len, len2; if (!(me instanceof ParamHash)) return new ParamHash(sString, sPropDelim, sValueDelim, bNoOverwrite,bRecurse); if (!arguments[LEN]) return me; if (sString && typeof sString == OBJ) return mix(new ParamHash("",sPropDelim,sValueDelim,bNoOverwrite,bRecurse),sString); sString = cstr(sString); sPropDelim = cstr(sPropDelim) || a; sValueDelim = cstr(sValueDelim) || eq; if (!sString) return me; if (sPropDelim != q && sValueDelim != q && sString.charAt(0) == q) sString = sString[ss](1); idx = sString[io](q); idx2 = sString[io](sValueDelim); if (idx != -1 && idx2 != -1 && idx > idx2) { sTemp = _es(sString[ss](idx2+1)); sTemp2 = sString.substr(0, idx2+1); sString = sTemp2 + sTemp; } else if (idx != -1) { sString = sString[ss](idx+1); return new ParamHash(sString, sPropDelim, sValueDelim, bNoOverwrite); }; if (sString.charAt(0) == sPropDelim) sString = sString[ss](1); pairs = sString.split(sPropDelim); cnt = pairs[LEN]; idx = 0; while (cnt--) { sTemp = pairs[idx++]; added = FALSE; doAdd = FALSE; if (sTemp) { nv = sTemp.split(sValueDelim); len = nv[LEN]; if (len > 2) { nm = _ue(nv[0]); nv.shift(); if (bRecurse) { /* Its possible that someone screws up and doesn't have a value encoded properly and but have multiple delimiters * As if recursion was going to take place. So here we know that's the case and try to handle it if we can detect * the end points as well */ sTemp2 = nm+sValueDelim; idx2 = sString[io](sTemp2); len = sTemp2[LEN]; sTemp3 = sString[ss](idx2+len); sTemp2 = sPropDelim+sPropDelim; len2 = sTemp2[LEN]; idx3 = sTemp3[io](sTemp2); if (idx3 != -1) { sTemp3 = sString.substr(idx2+len, idx3+len2); obj = new ParamHash(sTemp3, sPropDelim, sValueDelim, bNoOverwrite, bRecurse); sTemp3 = ""; len = 0; for (sTemp3 in obj) len++; if (len > 0) idx += (len-1); sTemp = obj; } else { sTemp = _ue(nv.join(sValueDelim)); } } else { sTemp = _ue(nv.join(sValueDelim)); } doAdd = TRUE; } else if (len == 2) { nm = _ue(nv[0]); sTemp = _ue(nv[1]); doAdd = TRUE; } if (doAdd) { if (bNoOverwrite) { if (!(nm in me)) { me[nm] = sTemp added = TRUE; }; } else { me[nm] = sTemp; added = TRUE; }; if (bRecurse && added && nm && sTemp && typeof sTemp != OBJ && (sTemp[io](sPropDelim) >= 0 || sTemp[io](sValueDelim) >= 0)) { me[nm] = new ParamHash(sTemp, sPropDelim, sValueDelim, bNoOverwrite, bRecurse); } } } }; } proto = ParamHash[PROTO]; /* * This internal function is used for the valueOf / toString methods of our ParamHash class. * */ /** * Converts a ParamHash object back into a string using the property and value delimiters specifed (defaults to "&" and "="). * Again this method works recursively. If an object is found as a property, it will convert that object into a ParamHash string * and then escape it. Note also that this class's valueOf method is equal to this method. * * @methodOf ParamHash# * @public * @function * @param {String} [sPropDelim="&"] The substring delimiter used to seperate properties. Default is "&". * @param {String} [sValueDelim="="] The substring delimited used to seperate values. Default is "=". * @param {Boolean} [escapeProp=false] Whether or not to ascii escape the name of a property * @param {Boolean} [dontEscapeValue=false] Do not escape values or properties automatically * @return {String} the encoded string representation of the object. * */ function toString(sPropDelim, sValueDelim, escapeProp, dontEscapeValue) { var prop, buffer = [], me = this, itemType, item; sPropDelim = sPropDelim || a; sValueDelim = sValueDelim || eq; for (prop in me) { item = me[prop]; itemType = typeof item; if (item && itemType == FUNC) continue; if (item && itemType == OBJ) { item = toString.apply(item, [sPropDelim,sValueDelim,escapeProp,dontEscapeValue]); } if (escapeProp) prop = _es(prop); if (!dontEscapeValue) item = _es(item); buffer.push(prop, sValueDelim, item, sPropDelim); } return cstr(buffer); } /** @ignore */ proto.toString = proto.valueOf = toString; lang = def(IAB_LIB + ".lang", { ParamHash: ParamHash, cstr: cstr, cnum: cnum, cbool: cbool, noop: noop, trim: trim, callable: callable, guid: guid, mix: mix, time: time, rand: rand, def: def, ns: ns, jssafe_html: jssafe_html, isArray: isArray }); /** * Whether or not we are running within an Internet Explorer browser environment * * @name $sf.env.isIE * @type {Boolean} * @static * @public * */ def("$sf.env", {isIE:isIE}); _cstr = cstr; _cnum = cnum; _callable = callable; })(); /** * @namespace $sf.env.ua Stores browser / user-agent information * @name $sf.env.ua * @requires $sf.lib.lang * */ (function() { /** * Convert a version string into a numeric value * * @name $sf.env.ua-_numberify * @static * @private * @function * @param {String} s The string representing a version number (e.g. 'major.minor.revision') * @returns {Number} * */ function _numberify(s) { var c = 0; return parseFloat(s.replace(/\./g, function() { return (c++ == 1) ? "" : "."; })); } /** * Wrapper method for returning values from a regular expression match safely. * * @name $sf.env.ua-_matchIt * @static * @private * @function * @param {String} str The string to match against * @param {RegExp} regEx The regular expression to use for matching * @param {Number} [idx] The index number of a match to pull from * @returns {String} * */ function _matchIt(str, regEx, idx) { var m = str && str.match(regEx); return (idx == NULL) ? m : ((m && m[idx]) || NULL); } /** * Wrapper method for testing a string against a regular expression * * @name $sf.env.ua-_testIt * @static * @private * @function * @param {RegExp} regEx The regular expression to test with * @param {String} str The string to test against * @param {Boolean} * */ function _testIt(regEx,str) { return regEx.test(str); } /** * Parse a user-agent string from the browser and gather pertinent browser, and OS information * * @name $sf.env.ua.parse * @static * @public * @function * @param {String} [subUA] An alternate user-agent string to parse. If no valid string is passed in, function will return an object based on the known user-agent * @returns {Object} <b>parsed</b> Browser and OS information<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.ie The major version number of the Internet Explorer browser being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.opera The major version number of the Opera browser being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.gecko The major version number of the Gecko (Firefox) browser being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.webkit The major version number of the WebKit browser being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.safari The major version number of the Safari browser being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.chrome The major version number of the Chrome browser being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.air The major version number of the AIR SDK being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.ipod Whether or not an iPod device is being used 1 for true, 0 for false.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.ipad Whether or not an iPad device is being used 1 for true, 0 for false.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.iphone Whether or not an iPhone device is being used 1 for true, 0 for false.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.android The major version number of the Android OS being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.webos The major version number of the WebOS being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.silk The major version number of the Silk browser being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.nodejs The major version number of the NodeJS environment being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.phantomjs The major version number of the PhantomJS environment being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {String} <b>parsed</b>.mobile A string representing whether or not the browser / os is a mobile device and it's type. Possible values are 'windows', 'android', 'symbos', 'linux', 'macintosh', 'rhino', 'gecko', 'Apple', 'chrome'.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.ios The major version number of the iOS being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Boolean} <b>parsed</b>.accel Whether or not the browser / environment in question is hardware accelerated.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @returns {Number} <b>parsed</b>.cajaVersion The major version number of the CAJA environment or 0 if not. * */ function parse_ua(subUA) { var ret = {}, match, date = new Date(); if (!subUA && cached_ua) return cached_ua; ret.ie = ret.opera = ret.gecko = ret.webkit = ret.safari = ret.chrome = ret.air = ret.ipod = ret.ipad = ret.iphone = ret.android = ret.webos = ret.silk = ret.nodejs = ret.phantomjs = 0; ret.mobile = ret.ios = ret.os = NULL; ret.accel = false; ret.caja = nav && nav.cajaVersion; ret.cks = FALSE; subUA = subUA || ua || ""; if (subUA) { if (_testIt(/windows|win32/i,subUA)) { ret.os = 'windows'; } else if (_testIt(/macintosh|mac_powerpc/i,subUA)) { ret.os = 'macintosh'; } else if (_testIt(/android/i,subUA)) { ret.os = 'android'; } else if (_testIt(/symbos/i, subUA)) { ret.os = 'symbos' } else if (_testIt(/linux/i, subUA)) { ret.os = 'linux'; } else if (_testIt(/rhino/i,subUA)) { ret.os = 'rhino'; } // Modern KHTML browsers should qualify as Safari X-Grade if (_testIt(/KHTML/,subUA)) { ret.webkit = 1; } if (_testIt(/IEMobile|XBLWP7/, subUA)) { ret.mobile = 'windows'; } if (_testIt(/Fennec/, subUA)) { ret.mobile = 'gecko'; } // Modern WebKit browsers are at least X-Grade match = _matchIt(subUA, /AppleWebKit\/([^\s]*)/, 1); if (match) { ret.webkit = _numberify(match); ret.safari = ret.webkit; if (_testIt(/PhantomJS/, subUA)) { match = _matchIt(subUA, /PhantomJS\/([^\s]*)/, 1); if (match) { ret.phantomjs = _numberify(match); } } // Mobile browser check if (_testIt(/ Mobile\//,subUA) || _testIt(/iPad|iPod|iPhone/, subUA)) { ret.mobile = 'Apple'; // iPhone or iPod Touch match = _matchIt(subUA, /OS ([^\s]*)/, 1); match = match && _numberify(match.replace('_', '.')); ret.ios = match; ret.ipad = ret.ipod = ret.iphone = 0; match = _matchIt(subUA,/iPad|iPod|iPhone/,0); if (match) { ret[match[TLC]()] = ret.ios; } } else { match = _matchIt(subUA, /NokiaN[^\/]*|Android \d\.\d|webOS\/\d\.\d/, 0); if (match) { // Nokia N-series, Android, webOS, ex: NokiaN95 ret.mobile = match; } if (_testIt(/webOS/,subUA)) { ret.mobile = 'WebOS'; match = _matchIt(subUA, /webOS\/([^\s]*);/, 1); if (match) { ret.webos = _numberify(match); } } if (_testIt(/ Android/,subUA)) { ret.mobile = 'Android'; match = _matchIt(subUA, /Android ([^\s]*);/, 1); if (match) { ret.android = _numberify(match); } } if (_testIt(/Silk/, subUA)) { match = _matchIt(subUA, /Silk\/([^\s]*)\)/, 1); if (match) { ret.silk = _numberify(match); } if (!ret.android) { ret.android = 2.34; //Hack for desktop mode in Kindle ret.os = 'Android'; } if (_testIt(/Accelerated=true/, subUA)) { ret.accel = true; } } } match = subUA.match(/(Chrome|CrMo)\/([^\s]*)/); if (match && match[1] && match[2]) { ret.chrome = _numberify(match[2]); // Chrome ret.safari = 0; //Reset safari back to 0 if (match[1] === 'CrMo') { ret.mobile = 'chrome'; } } else { match = _matchIt(subUA,/AdobeAIR\/([^\s]*)/); if (match) { ret.air = match[0]; // Adobe AIR 1.0 or better } } } if (!ret.webkit) { match = _matchIt(subUA, /Opera[\s\/]([^\s]*)/, 1); if (match) { ret.opera = _numberify(match); match = _matchIt(subUA, /Opera Mini[^;]*/, 0); if (match) { ret.mobile = match; // ex: Opera Mini/2.0.4509/1316 } } else { // not opera or webkit match = _matchIt(subUA, /MSIE\s([^;]*)/, 1); if (match) { ret.ie = _numberify(match); } else { // not opera, webkit, or ie match = _matchIt(subUA, /Gecko\/([^\s]*)/); if (match) { ret.gecko = 1; // Gecko detected, look for revision match = _matchIt(subUA, /rv:([^\s\)]*)/, 1); if (match) { ret.gecko = _numberify(match); } } } } } } try { date.setTime(date.getTime() + 1000); d.cookie = cstr(["sf_ck_tst=test; expires=", date.toGMTString(), "; path=/"]); if (d.cookie.indexOf("sf_ck_tst") != -1) ret.cks = TRUE; } catch (e) { ret.cks = FALSE; } try { if (typeof process == OBJ) { if (process.versions && process.versions.node) { //NodeJS ret.os = process.platform; ret.nodejs = numberify(process.versions.node); } } } catch (e) { ret.nodejs = 0; } return ret; } /** * The major version number of the Internet Explorer browser being used, or 0 if not. * * @name $sf.env.ua.ie * @type {Number} * @public * @static * */ /** * The major version number of the Opera browser being used, or 0 if not.<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * @name $sf.env.ua.opera * @type {Number} * @public * @static */ /** * The major version number of the Gecko (Firefox) browser being used, or 0 if not. * @name $sf.env.ua.gecko * @type {Number} * @public * @static */ /** * The major version number of the WebKit browser being used, or 0 if not. * @name $sf.env.ua.webkit * @type {Number} * @public * @static */ /** * The major version number of the Safari browser being used, or 0 if not. * @name $sf.env.ua.safari * @type {Number} * @public * @static */ /** * The major version number of the Chrome browser being used, or 0 if not. * @name $sf.env.ua.chrome * @type {Number} * @public * @static */ /** * The major version number of the AIR SDK being used, or 0 if not. * @name $sf.env.ua.air * @type {Number} * @public * @static */ /** * Whether or not an iPod device is being used, 0 for false, &gt; 0 == true * @name $sf.env.ua.ipod * @type {Number} * @public * @static * */ /** * Whether or not an iPad device is being used, 0 for false, &gt; 0 == true * @name $sf.env.ua.ipad * @type {Number} * @public * @static * */ /** * Whether or not an iPhone device is being used, 0 for false, &gt; 0 == true * @name $sf.env.ua.iphone * @type {Number} * @public * @static * */ /** * The major version number of the Android OS being used, or 0 if not. * @name $sf.env.ua.android * @type {Number} * @public * @static */ /** * The major version number of the WebOS being used, or 0 if not. * @name $sf.env.ua.webos * @type {Number} * @public * @static */ /** * The major version number of the Silk browser being used, or 0 if not. * @name $sf.env.ua.silk * @type {Number} * @public * @static */ /** * The major version number of the NodeJS environment being used, or 0 if not. * @name $sf.env.ua.nodejs * @type {Number} * @public * @static */ /** * The major version number of the PhantomJS environment being used, or 0 if not. * @name $sf.env.ua.phantomjs * @type {Number} * @public * @static */ /** * A string representing whether or not the browser / os is a mobile device and it's type. Possible values are 'windows', 'android', 'symbos', 'linux', 'macintosh', 'rhino', 'gecko', 'Apple', 'chrome'. * * @name $sf.env.ua.mobile * @type {String} * @public * @static */ /** * The major version number of the iOS being used, or 0 if not. * @name $sf.env.ua.ios * @type {Number} * @public * @static */ /** * Whether or not the browser / environment in question is hardware accelerated. * @name $sf.env.ua.accel * @type {Boolean} * @public * @static */ /** * The major version number of the CAJA environment or 0 if not * @name $sf.env.ua.cajaVersion * @type {Number} * @public * @static */ cached_ua = parse_ua(); cached_ua.parse = parse_ua; lang.def(IAB_ENV + ".ua", cached_ua, NULL, TRUE); })(); /** * @namespace $sf.lib.dom Defines helper functions / objects for common browser web-page / DOM interactions * @name $sf.lib.dom * @requires $sf.lib.lang * */ (function() { /** * Clear out the timer function used as a fallback when ready state of the DOM * cannot be directly detected * * @name $sf.lib.dom-_clear_ready_timer_check * @private * @static * @function * */ function _clear_ready_timer_check() { if (dom_ready_chk_timer_id) { clearTimeout(dom_ready_chk_timer_id); dom_ready_chk_timer_id = 0; } } function _handle_dom_load_evt(evt) { detach(win, "load", _handle_dom_load_evt); detach(win, DCLDED, _handle_dom_load_evt); dom_is_ready = TRUE; } /** * Checks to see if the DOM is ready to be manipulated, without the need for event hooking. * Often times you'll see folks use the onload event or DOMContentLoaded event. However * the problem with those, is that your JavaScript code may have been loaded asynchronously, * after either one of those events have fired, and in which case you still don't know if the DOM is really * ready. Most modern browsers (including IE), implement a document.readyState property that we can * check, but not all. In the case where this property is not implemented, we do a series of node * checks and tag counts via timers. Of course this means that on the very 1st call, we will always * appear to be not ready eventhough the DOM itself may be in a ready state, but our timeout interval * is small enough that this is OK. * * @name $sf.lib.dom-_ready_state_check * @private * @static * @function * */ function _ready_state_check() { var b, kids, tag_cnt, lst, e; _clear_ready_timer_check(); if (dom_ready_chk_tries >= dom_ready_chk_max_tries) { dom_last_known_child_node = NULL; dom_is_ready = TRUE; } if (dom_is_ready === NULL) { try { b = (d && d.body); kids = (b && tags("*",b)); tag_cnt = (kids && kids[LEN]); lst = (b && b.lastChild); } catch (e) { dom_last_known_tag_count = 0; dom_last_known_child_node = NULL; } if (dom_last_known_tag_count && tag_cnt == dom_last_known_tag_count && lst == dom_last_known_child_node) { dom_last_known_child_node = NULL; dom_is_ready = TRUE; } else { dom_last_known_tag_count = tag_cnt; dom_last_known_child_node = lst; dom_ready_chk_tries += 1; dom_ready_chk_timer_id = setTimeout(_ready_state_check, dom_ready_chk_try_interval); } } else { dom_last_known_child_node = NULL; } } /** * Detach onload handlers on iframes that we have created * * @name $sf.lib.dom.iframes-_unbind_iframe_onload * @private * @static * @function * @param {HTMLElement} el the iframe element to unbind from * */ function _unbind_iframe_onload(el) { var id = attr(el,"id"), oldCB; oldCB = (id && iframe_cbs_attached[id]); if (oldCB) { detach(el, "load", oldCB); iframe_cbs_attached[id] = NULL; delete iframe_cbs_attached[id]; } } /** * A default onload event handler for IFrames. We don't * want to attach to onload events for IFrames via attributes * b/c we don't want others to see what handlers are there. * In turn we also make sure the "this" reference for the outside * handle gets set properly, and it allows us to make sure * that unbinding of the event handler also gets handled always * so as not to create memory leak issues. * * @name $sf.lib.dom.iframes-_bind_iframe_onload * @private * @static * @function * @param {HTMLElement} el the iframe element to bind too * @param {Function} cb The onload handler from the outside * */ function _bind_iframe_onload(el, cb) { var newCB, id; if (_callable(cb)) { /** @ignore */ newCB = function(evt) { var tgt = evtTgt(evt), e; _unbind_iframe_onload(tgt); if (tgt && cb) { try { cb.call(tgt, evt); } catch (e) { } } tgt = el = cb = newCB = id = NULL; }; id = attr(el,"id"); _unbind_iframe_onload(el); if (id) iframe_cbs_attached[id] = newCB; attach(el, "load", newCB); } newCB = NULL; } /** * Return the element reference passed in, and if its a string value passed * in use that to lookup the element by id attribute. * * @name $sf.lib.dom-_byID * @private * @static * @function * @param {HTMLElement|String} el the element id / element reference * @return {HTMLElement|el} * */ function _byID(el) { return (el && typeof el == STR) ? elt(el) || el : el; } /** * A proxy wrapper for calling into the cross-domain messaging host library * * @name $sf.lib.dom.iframes-_call_xmsg_host * @private * @static * @function * @param {String} methName The method name in the msg host library to call * @param {*} arg1 An arbitrary argument to pass to said method as the 1st arg * @param {*} arg2 An arbitrary argument to pass to said method as the 2nd arg * @param {*} arg3 An arbitrary argument to pass to said method as the 3rd arg * @return {*} whatever comes back from the method * */ function _call_xmsg_host(methName, arg1, arg2, arg3) { var e; try { if (!iframe_msg_host_lib) iframe_msg_host_lib = dom.msghost; } catch (e) { iframe_msg_host_lib = NULL; } if (win != top) return; return methName && iframe_msg_host_lib && iframe_msg_host_lib[methName] && iframe_msg_host_lib[methName](arg1,arg2,arg3); } /** * Retrieve a document for a given HTML Element * * @memberOf $sf.lib.dom * @exports doc as $sf.lib.dom.doc * @static * @public * @function * @param {HTMLElement} el the HTML element for which you wish to find it's parent document * @return {Document|null} null if nothing found * */ function doc(el) { var d = NULL; try { if (el) { if (el[NODE_TYPE] == 9) { d = el; } else { d = el[DOC] || el.ownerDocument || NULL; } } } catch (e) { d = NULL; } return d; } /** * Retrieve the host window object for a given HTML Element/document. Note that this is NOT the same as $sf.lib.dom.iframes.view, which * returns the window reference INSIDE the IFRAME element. * * @memberOf $sf.lib.dom * @exports view as $sf.lib.dom.vie