Changeset 56074
- Timestamp:
- 06/27/2023 05:22:59 PM (20 months ago)
- Location:
- trunk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/.jshintrc
r56049 r56074 25 25 "export": false, 26 26 "module": false, 27 "require": false 27 "require": false, 28 "WorkerGlobalScope": false, 29 "self": false, 30 "OffscreenCanvas": false, 31 "Promise": false 28 32 } 29 33 } -
trunk/src/js/_enqueues/lib/emoji-loader.js
r55241 r56074 3 3 */ 4 4 5 ( function( window, document, settings ) { 6 var src, ready, ii, tests; 7 8 // Create a canvas element for testing native browser support of emoji. 9 var canvas = document.createElement( 'canvas' ); 10 var context = canvas.getContext && canvas.getContext( '2d' ); 5 /** 6 * Emoji Settings as exported in PHP via _print_emoji_detection_script(). 7 * @typedef WPEmojiSettings 8 * @type {object} 9 * @property {?object} source 10 * @property {?string} source.concatemoji 11 * @property {?string} source.twemoji 12 * @property {?string} source.wpemoji 13 * @property {?boolean} DOMReady 14 * @property {?Function} readyCallback 15 */ 16 17 /** 18 * Support tests. 19 * @typedef SupportTests 20 * @type {object} 21 * @property {?boolean} flag 22 * @property {?boolean} emoji 23 */ 24 25 /** 26 * IIFE to detect emoji support and load Twemoji if needed. 27 * 28 * @param {Window} window 29 * @param {Document} document 30 * @param {WPEmojiSettings} settings 31 */ 32 ( function wpEmojiLoader( window, document, settings ) { 33 if ( typeof Promise === 'undefined' ) { 34 return; 35 } 36 37 var sessionStorageKey = 'wpEmojiSettingsSupports'; 38 var tests = [ 'flag', 'emoji' ]; 39 40 /** 41 * Checks whether the browser supports offloading to a Worker. 42 * 43 * @since 6.3.0 44 * 45 * @private 46 * 47 * @returns {boolean} 48 */ 49 function supportsWorkerOffloading() { 50 return ( 51 typeof Worker !== 'undefined' && 52 typeof OffscreenCanvas !== 'undefined' && 53 typeof URL !== 'undefined' && 54 URL.createObjectURL && 55 typeof Blob !== 'undefined' 56 ); 57 } 58 59 /** 60 * @typedef SessionSupportTests 61 * @type {object} 62 * @property {number} timestamp 63 * @property {SupportTests} supportTests 64 */ 65 66 /** 67 * Get support tests from session. 68 * 69 * @since 6.3.0 70 * 71 * @private 72 * 73 * @returns {?SupportTests} Support tests, or null if not set or older than 1 week. 74 */ 75 function getSessionSupportTests() { 76 if ( 77 typeof sessionStorage !== 'undefined' && 78 sessionStorageKey in sessionStorage 79 ) { 80 try { 81 /** @type {SessionSupportTests} */ 82 var item = JSON.parse( 83 sessionStorage.getItem( sessionStorageKey ) 84 ); 85 if ( 86 typeof item === 'object' && 87 typeof item.timestamp === 'number' && 88 new Date().valueOf() < item.timestamp + 604800 && // Note: Number is a week in seconds. 89 typeof item.supportTests === 'object' 90 ) { 91 return item.supportTests; 92 } 93 } catch ( e ) {} 94 } 95 return null; 96 } 97 98 /** 99 * Persist the supports in session storage. 100 * 101 * @since 6.3.0 102 * 103 * @private 104 * 105 * @param {SupportTests} supportTests Support tests. 106 */ 107 function setSessionSupportTests( supportTests ) { 108 if ( typeof sessionStorage !== 'undefined' ) { 109 try { 110 /** @type {SessionSupportTests} */ 111 var item = { 112 supportTests: supportTests, 113 timestamp: new Date().valueOf() 114 }; 115 116 sessionStorage.setItem( 117 sessionStorageKey, 118 JSON.stringify( item ) 119 ); 120 } catch ( e ) {} 121 } 122 } 11 123 12 124 /** 13 125 * Checks if two sets of Emoji characters render the same visually. 14 126 * 127 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing 128 * scope. Everything must be passed by parameters. 129 * 15 130 * @since 4.9.0 16 131 * 17 132 * @private 18 133 * 134 * @param {CanvasRenderingContext2D} context 2D Context. 19 135 * @param {string} set1 Set of Emoji to test. 20 136 * @param {string} set2 Set of Emoji to test. … … 22 138 * @return {boolean} True if the two sets render the same. 23 139 */ 24 function emojiSetsRenderIdentically( set1, set2 ) {140 function emojiSetsRenderIdentically( context, set1, set2 ) { 25 141 // Cleanup from previous test. 26 context.clearRect( 0, 0, c anvas.width,canvas.height );142 context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); 27 143 context.fillText( set1, 0, 0 ); 28 var rendered1 = canvas.toDataURL(); 144 var rendered1 = new Uint32Array( 145 context.getImageData( 146 0, 147 0, 148 context.canvas.width, 149 context.canvas.height 150 ).data 151 ); 29 152 30 153 // Cleanup from previous test. 31 context.clearRect( 0, 0, c anvas.width,canvas.height );154 context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); 32 155 context.fillText( set2, 0, 0 ); 33 var rendered2 = canvas.toDataURL(); 34 35 return rendered1 === rendered2; 156 var rendered2 = new Uint32Array( 157 context.getImageData( 158 0, 159 0, 160 context.canvas.width, 161 context.canvas.height 162 ).data 163 ); 164 165 return rendered1.every( function ( rendered2Data, index ) { 166 return rendered2Data === rendered2[ index ]; 167 } ); 36 168 } 37 169 … … 39 171 * Determines if the browser properly renders Emoji that Twemoji can supplement. 40 172 * 173 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing 174 * scope. Everything must be passed by parameters. 175 * 41 176 * @since 4.2.0 42 177 * 43 178 * @private 44 179 * 180 * @param {CanvasRenderingContext2D} context 2D Context. 45 181 * @param {string} type Whether to test for support of "flag" or "emoji". 46 182 * 47 183 * @return {boolean} True if the browser can render emoji, false if it cannot. 48 184 */ 49 function browserSupportsEmoji( type ) {185 function browserSupportsEmoji( context, type ) { 50 186 var isIdentical; 51 187 52 if ( ! context || ! context.fillText ) { 53 return false; 54 } 188 switch ( type ) { 189 case 'flag': 190 /* 191 * Test for Transgender flag compatibility. Added in Unicode 13. 192 * 193 * To test for support, we try to render it, and compare the rendering to how it would look if 194 * the browser doesn't render it correctly (white flag emoji + transgender symbol). 195 */ 196 isIdentical = emojiSetsRenderIdentically( 197 context, 198 '\uD83C\uDFF3\uFE0F\u200D\u26A7\uFE0F', // as a zero-width joiner sequence 199 '\uD83C\uDFF3\uFE0F\u200B\u26A7\uFE0F' // separated by a zero-width space 200 ); 201 202 if ( isIdentical ) { 203 return false; 204 } 205 206 /* 207 * Test for UN flag compatibility. This is the least supported of the letter locale flags, 208 * so gives us an easy test for full support. 209 * 210 * To test for support, we try to render it, and compare the rendering to how it would look if 211 * the browser doesn't render it correctly ([U] + [N]). 212 */ 213 isIdentical = emojiSetsRenderIdentically( 214 context, 215 '\uD83C\uDDFA\uD83C\uDDF3', // as the sequence of two code points 216 '\uD83C\uDDFA\u200B\uD83C\uDDF3' // as the two code points separated by a zero-width space 217 ); 218 219 if ( isIdentical ) { 220 return false; 221 } 222 223 /* 224 * Test for English flag compatibility. England is a country in the United Kingdom, it 225 * does not have a two letter locale code but rather a five letter sub-division code. 226 * 227 * To test for support, we try to render it, and compare the rendering to how it would look if 228 * the browser doesn't render it correctly (black flag emoji + [G] + [B] + [E] + [N] + [G]). 229 */ 230 isIdentical = emojiSetsRenderIdentically( 231 context, 232 // as the flag sequence 233 '\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F', 234 // with each code point separated by a zero-width space 235 '\uD83C\uDFF4\u200B\uDB40\uDC67\u200B\uDB40\uDC62\u200B\uDB40\uDC65\u200B\uDB40\uDC6E\u200B\uDB40\uDC67\u200B\uDB40\uDC7F' 236 ); 237 238 return ! isIdentical; 239 case 'emoji': 240 /* 241 * Why can't we be friends? Everyone can now shake hands in emoji, regardless of skin tone! 242 * 243 * To test for Emoji 14.0 support, try to render a new emoji: Handshake: Light Skin Tone, Dark Skin Tone. 244 * 245 * The Handshake: Light Skin Tone, Dark Skin Tone emoji is a ZWJ sequence combining 🫱 Rightwards Hand, 246 * 🏻 Light Skin Tone, a Zero Width Joiner, 🫲 Leftwards Hand, and 🏿 Dark Skin Tone. 247 * 248 * 0x1FAF1 == Rightwards Hand 249 * 0x1F3FB == Light Skin Tone 250 * 0x200D == Zero-Width Joiner (ZWJ) that links the code points for the new emoji or 251 * 0x200B == Zero-Width Space (ZWS) that is rendered for clients not supporting the new emoji. 252 * 0x1FAF2 == Leftwards Hand 253 * 0x1F3FF == Dark Skin Tone. 254 * 255 * When updating this test for future Emoji releases, ensure that individual emoji that make up the 256 * sequence come from older emoji standards. 257 */ 258 isIdentical = emojiSetsRenderIdentically( 259 context, 260 '\uD83E\uDEF1\uD83C\uDFFB\u200D\uD83E\uDEF2\uD83C\uDFFF', // as the zero-width joiner sequence 261 '\uD83E\uDEF1\uD83C\uDFFB\u200B\uD83E\uDEF2\uD83C\uDFFF' // separated by a zero-width space 262 ); 263 264 return ! isIdentical; 265 } 266 267 return false; 268 } 269 270 /** 271 * Checks emoji support tests. 272 * 273 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing 274 * scope. Everything must be passed by parameters. 275 * 276 * @since 6.3.0 277 * 278 * @private 279 * 280 * @param {string[]} tests Tests. 281 * 282 * @return {SupportTests} Support tests. 283 */ 284 function testEmojiSupports( tests ) { 285 var canvas; 286 if ( 287 typeof WorkerGlobalScope !== 'undefined' && 288 self instanceof WorkerGlobalScope 289 ) { 290 canvas = new OffscreenCanvas( 300, 150 ); // Dimensions are default for HTMLCanvasElement. 291 } else { 292 canvas = document.createElement( 'canvas' ); 293 } 294 295 var context = canvas.getContext( '2d', { willReadFrequently: true } ); 55 296 56 297 /* … … 62 303 context.font = '600 32px Arial'; 63 304 64 switch ( type ) { 65 case 'flag': 66 /* 67 * Test for Transgender flag compatibility. Added in Unicode 13. 68 * 69 * To test for support, we try to render it, and compare the rendering to how it would look if 70 * the browser doesn't render it correctly (white flag emoji + transgender symbol). 71 */ 72 isIdentical = emojiSetsRenderIdentically( 73 '\uD83C\uDFF3\uFE0F\u200D\u26A7\uFE0F', // as a zero-width joiner sequence 74 '\uD83C\uDFF3\uFE0F\u200B\u26A7\uFE0F' // separated by a zero-width space 75 ); 76 77 if ( isIdentical ) { 78 return false; 79 } 80 81 /* 82 * Test for UN flag compatibility. This is the least supported of the letter locale flags, 83 * so gives us an easy test for full support. 84 * 85 * To test for support, we try to render it, and compare the rendering to how it would look if 86 * the browser doesn't render it correctly ([U] + [N]). 87 */ 88 isIdentical = emojiSetsRenderIdentically( 89 '\uD83C\uDDFA\uD83C\uDDF3', // as the sequence of two code points 90 '\uD83C\uDDFA\u200B\uD83C\uDDF3' // as the two code points separated by a zero-width space 91 ); 92 93 if ( isIdentical ) { 94 return false; 95 } 96 97 /* 98 * Test for English flag compatibility. England is a country in the United Kingdom, it 99 * does not have a two letter locale code but rather a five letter sub-division code. 100 * 101 * To test for support, we try to render it, and compare the rendering to how it would look if 102 * the browser doesn't render it correctly (black flag emoji + [G] + [B] + [E] + [N] + [G]). 103 */ 104 isIdentical = emojiSetsRenderIdentically( 105 // as the flag sequence 106 '\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F', 107 // with each code point separated by a zero-width space 108 '\uD83C\uDFF4\u200B\uDB40\uDC67\u200B\uDB40\uDC62\u200B\uDB40\uDC65\u200B\uDB40\uDC6E\u200B\uDB40\uDC67\u200B\uDB40\uDC7F' 109 ); 110 111 return ! isIdentical; 112 case 'emoji': 113 /* 114 * Why can't we be friends? Everyone can now shake hands in emoji, regardless of skin tone! 115 * 116 * To test for Emoji 14.0 support, try to render a new emoji: Handshake: Light Skin Tone, Dark Skin Tone. 117 * 118 * The Handshake: Light Skin Tone, Dark Skin Tone emoji is a ZWJ sequence combining 🫱 Rightwards Hand, 119 * 🏻 Light Skin Tone, a Zero Width Joiner, 🫲 Leftwards Hand, and 🏿 Dark Skin Tone. 120 * 121 * 0x1FAF1 == Rightwards Hand 122 * 0x1F3FB == Light Skin Tone 123 * 0x200D == Zero-Width Joiner (ZWJ) that links the code points for the new emoji or 124 * 0x200B == Zero-Width Space (ZWS) that is rendered for clients not supporting the new emoji. 125 * 0x1FAF2 == Leftwards Hand 126 * 0x1F3FF == Dark Skin Tone. 127 * 128 * When updating this test for future Emoji releases, ensure that individual emoji that make up the 129 * sequence come from older emoji standards. 130 */ 131 isIdentical = emojiSetsRenderIdentically( 132 '\uD83E\uDEF1\uD83C\uDFFB\u200D\uD83E\uDEF2\uD83C\uDFFF', // as the zero-width joiner sequence 133 '\uD83E\uDEF1\uD83C\uDFFB\u200B\uD83E\uDEF2\uD83C\uDFFF' // separated by a zero-width space 134 ); 135 136 return ! isIdentical; 137 } 138 139 return false; 305 var supports = {}; 306 tests.forEach( function ( test ) { 307 supports[ test ] = browserSupportsEmoji( context, test ); 308 } ); 309 return supports; 140 310 } 141 311 … … 147 317 * @since 4.2.0 148 318 * 149 * @param {Object} src The url where the script is located. 319 * @param {string} src The url where the script is located. 320 * 150 321 * @return {void} 151 322 */ 152 323 function addScript( src ) { 153 324 var script = document.createElement( 'script' ); 154 155 325 script.src = src; 156 script.defer = script.type = 'text/javascript'; 157 document.getElementsByTagName( 'head' )[0].appendChild( script ); 158 } 159 160 tests = Array( 'flag', 'emoji' ); 326 script.defer = true; 327 document.head.appendChild( script ); 328 } 161 329 162 330 settings.supports = { … … 165 333 }; 166 334 167 /* 168 * Tests the browser support for flag emojis and other emojis, and adjusts the 169 * support settings accordingly. 170 */ 171 for( ii = 0; ii < tests.length; ii++ ) { 172 settings.supports[ tests[ ii ] ] = browserSupportsEmoji( tests[ ii ] ); 173 174 settings.supports.everything = settings.supports.everything && settings.supports[ tests[ ii ] ]; 175 176 if ( 'flag' !== tests[ ii ] ) { 177 settings.supports.everythingExceptFlag = settings.supports.everythingExceptFlag && settings.supports[ tests[ ii ] ]; 178 } 179 } 180 181 settings.supports.everythingExceptFlag = settings.supports.everythingExceptFlag && ! settings.supports.flag; 182 183 // Sets DOMReady to false and assigns a ready function to settings. 184 settings.DOMReady = false; 185 settings.readyCallback = function() { 186 settings.DOMReady = true; 187 }; 188 189 // When the browser can not render everything we need to load a polyfill. 190 if ( ! settings.supports.everything ) { 191 ready = function() { 192 settings.readyCallback(); 193 }; 194 195 /* 196 * Cross-browser version of adding a dom ready event. 197 */ 198 if ( document.addEventListener ) { 199 document.addEventListener( 'DOMContentLoaded', ready, false ); 200 window.addEventListener( 'load', ready, false ); 201 } else { 202 window.attachEvent( 'onload', ready ); 203 document.attachEvent( 'onreadystatechange', function() { 204 if ( 'complete' === document.readyState ) { 205 settings.readyCallback(); 335 // Create a promise for DOMContentLoaded since the worker logic may finish after the event has fired. 336 var domReadyPromise = new Promise( function ( resolve ) { 337 document.addEventListener( 'DOMContentLoaded', resolve, { 338 once: true 339 } ); 340 } ); 341 342 // Obtain the emoji support from the browser, asynchronously when possible. 343 new Promise( function ( resolve ) { 344 var supportTests = getSessionSupportTests(); 345 if ( supportTests ) { 346 resolve( supportTests ); 347 return; 348 } 349 350 if ( supportsWorkerOffloading() ) { 351 try { 352 /* 353 * Note that this string contains the real source code for the 354 * copied functions, _not_ a string representation of them. This 355 * is because it's not possible to transfer a Function across 356 * threads. The lack of quotes is intentional. The function names 357 * are copied to variable names since minification will munge the 358 * function names, thus breaking the ability for the functions to 359 * refer to each other. 360 */ 361 var workerScript = 362 'var emojiSetsRenderIdentically = ' + emojiSetsRenderIdentically + ';' + 363 'var browserSupportsEmoji = ' + browserSupportsEmoji + ';' + 364 'var testEmojiSupports = ' + testEmojiSupports + ';' + 365 'postMessage(testEmojiSupports(' + JSON.stringify(tests) + '));'; 366 var blob = new Blob( [ workerScript ], { 367 type: 'text/javascript' 368 } ); 369 var worker = new Worker( URL.createObjectURL( blob ) ); 370 worker.onmessage = function ( event ) { 371 supportTests = event.data; 372 setSessionSupportTests( supportTests ); 373 resolve( supportTests ); 374 }; 375 return; 376 } catch ( e ) {} 377 } 378 379 supportTests = testEmojiSupports( tests ); 380 setSessionSupportTests( supportTests ); 381 resolve( supportTests ); 382 } ) 383 // Once the browser emoji support has been obtained from the session, finalize the settings. 384 .then( function ( supportTests ) { 385 /* 386 * Tests the browser support for flag emojis and other emojis, and adjusts the 387 * support settings accordingly. 388 */ 389 for ( var test in supportTests ) { 390 settings.supports[ test ] = supportTests[ test ]; 391 392 settings.supports.everything = 393 settings.supports.everything && settings.supports[ test ]; 394 395 if ( 'flag' !== test ) { 396 settings.supports.everythingExceptFlag = 397 settings.supports.everythingExceptFlag && 398 settings.supports[ test ]; 206 399 } 207 } ); 208 } 209 210 src = settings.source || {}; 211 212 if ( src.concatemoji ) { 213 addScript( src.concatemoji ); 214 } else if ( src.wpemoji && src.twemoji ) { 215 addScript( src.twemoji ); 216 addScript( src.wpemoji ); 217 } 218 } 219 400 } 401 402 settings.supports.everythingExceptFlag = 403 settings.supports.everythingExceptFlag && 404 ! settings.supports.flag; 405 406 // Sets DOMReady to false and assigns a ready function to settings. 407 settings.DOMReady = false; 408 settings.readyCallback = function () { 409 settings.DOMReady = true; 410 }; 411 } ) 412 .then( function () { 413 return domReadyPromise; 414 } ) 415 .then( function () { 416 // When the browser can not render everything we need to load a polyfill. 417 if ( ! settings.supports.everything ) { 418 settings.readyCallback(); 419 420 var src = settings.source || {}; 421 422 if ( src.concatemoji ) { 423 addScript( src.concatemoji ); 424 } else if ( src.wpemoji && src.twemoji ) { 425 addScript( src.twemoji ); 426 addScript( src.wpemoji ); 427 } 428 } 429 } ); 220 430 } )( window, document, window._wpemojiSettings );
Note: See TracChangeset
for help on using the changeset viewer.