1 (function (GCN) { 2 3 'use strict'; 4 5 /** 6 * Only grows, never shrinks. 7 * @private 8 * @type {number} 9 */ 10 var uniqueIdCounter = 0; 11 12 /** 13 * Generates a unique id with an optional prefix. 14 * 15 * The returned value is only unique among other returned values, 16 * not globally. 17 * 18 * @public 19 * @param {string} 20 * Optional prefix for the id to be generated. 21 * @return {string} 22 * Never the same string more than once. 23 */ 24 function uniqueId(prefix) { 25 return (prefix || '') + (++uniqueIdCounter); 26 } 27 28 function escapePropertyName(name) { 29 return name.replace(/\./g, '\\.'); 30 } 31 32 /** 33 * A regular expression to identify executable script tags. 34 * 35 * @type {RegExp} 36 */ 37 var rgxpScriptType = /\/(java|ecma)script/i; 38 39 /** 40 * A regular expression to find script tags. 41 * 42 * @type {RegExp} 43 */ 44 var rgxpScriptTag = new RegExp('<script(\\s[^>]*?)?>', 'ig'); 45 46 /** 47 * A regular expression to find script types. 48 * 49 * @type {RegExp} 50 */ 51 var rgxpType = new RegExp( 52 ' type\\s*=\\s*' 53 + '[\\"\\\']' 54 + '([^\\"\\\']*[^\\s][^\\"\\\']*)' 55 + '[\\"\\\']', 56 'i' 57 ); 58 59 var rand = Math.random().toString().replace('.', ''); 60 61 /** 62 * Places sentinal strings in front of every executable script tag. 63 * 64 * @param {string} html HTML markup 65 * @return {string} HTML string with marked <script> tags. 66 */ 67 function markScriptTagLocations(html) { 68 var i = 0; 69 return html.replace(rgxpScriptTag, function (str, substr, offset) { 70 var type = substr && substr.match(rgxpType); 71 if (!type || rgxpScriptType.test(type)) { 72 return rand + (i++) + str; 73 } 74 return str; 75 }); 76 } 77 78 /** 79 * Masks a <script> tag's `type' attribute by replacing it with a random 80 * string. 81 * 82 * Masking script tags is don to prevent them from being handled specially 83 * by jQuery which removes javascript/ecmascript tags when appending DOM 84 * elements into the document, but executes them. 85 * 86 * unmakeScriptType() does the reverse of this. 87 * 88 * @param {jQuery.<HTMLElement>} $script jQuery unit set containing the 89 * <script> tag that is to have its 90 * type attribute masked. 91 */ 92 function maskScriptType($script) { 93 var type = $script.attr('type'); 94 $script.attr('type', rand).attr('data-origtype', type); 95 } 96 97 /** 98 * Restores a <script> tag's original type attribute value, if it had been 99 * masked using maskScriptType(). 100 * 101 * Essentially the reverse of maskScriptType(). 102 * 103 * @param {jQuery.<HTMLElement>} $script jQuery unit set containing the 104 * <script> tag that is to have its 105 * type attribute unmasked. 106 */ 107 function unmaskScriptType($script) { 108 var orig = $script.attr('data-origtype'); 109 if (typeof orig === 'string') { 110 $script.attr('type', orig).removeAttr('data-origtype'); 111 } else { 112 $script.removeAttr('type'); 113 } 114 } 115 116 /** 117 * Replaces the type attribute of <script> tags with a value that will 118 * protect them from being specially handled by jQuery. 119 * 120 * @param {string} html Markup 121 * @return {string} Markup with <script> tags protected from jQuery. 122 */ 123 function protectScriptTags(html, $scripts) { 124 var i; 125 var $script; 126 var type; 127 for (i = 0; i < $scripts.length; i++) { 128 $script = $scripts.eq(i); 129 type = $script.attr('type'); 130 if (!type || rgxpScriptType.test(type)) { 131 maskScriptType($script); 132 html = html.replace(rand + i, $script[0].outerHTML); 133 unmaskScriptType($script); 134 } 135 } 136 return html; 137 } 138 139 /** 140 * Restores the type attribute for <script> tags that have been processed 141 * via protectScriptTags(). 142 * 143 * @param {jQuery.<HTMLElement>} $element Root HTMLElement in which to 144 * restore <script> tags. 145 */ 146 function restoreScriptTagTypes($element) { 147 var $scripts = $element.find('script[type="' + rand + '"]'); 148 var $script; 149 var i; 150 for (i = 0; i < $scripts.length; i++) { 151 $script = $scripts.eq(i); 152 $script.removeClass(rand + i); 153 unmaskScriptType($script); 154 } 155 } 156 157 /** 158 * Joins the innerHTML of multiple elements. 159 * 160 * @param {jQuery.<HTMLElement>} $elements 161 */ 162 function joinContents($elements) { 163 var contents = ''; 164 $elements.each(function () { 165 contents += jQuery(this).html(); 166 }); 167 return contents; 168 } 169 170 /** 171 * Inserts the inner HTML of the given HTML markup while preserving 172 * <script> tags, and still allowing jQuery to execute them. 173 * 174 * @param {jQuery.<HTMLElement>} $element 175 * @param {string} html 176 */ 177 function insertInnerHTMLWithScriptTags($element, html) { 178 if (!rgxpScriptTag.test(html)) { 179 $element.html(jQuery(html).contents()); 180 return; 181 } 182 var marked = markScriptTagLocations(html); 183 var $scripts = jQuery(html).filter('script'); 184 var $html = jQuery(marked); 185 var contents = joinContents($html.filter(':not(script)')); 186 contents = protectScriptTags(contents, $scripts); 187 $element.html(contents); 188 189 // Trap script errors originating from rendered tags. 190 try { 191 $element.append($scripts); 192 } catch (ex) { 193 var _console = 'console'; 194 if (window[_console]) { 195 window[_console].error(ex); 196 } 197 } 198 199 restoreScriptTagTypes($element); 200 } 201 202 GCN.uniqueId = uniqueId; 203 GCN.escapePropertyName = escapePropertyName; 204 GCN.insertInnerHTMLWithScriptTags = insertInnerHTMLWithScriptTags; 205 206 }(GCN)); 207