[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /** 2 * @output wp-includes/js/wp-emoji-loader.js 3 */ 4 5 /* eslint-env es6 */ 6 7 // Note: This is loaded as a script module, so there is no need for an IIFE to prevent pollution of the global scope. 8 9 /** 10 * Emoji Settings as exported in PHP via _print_emoji_detection_script(). 11 * @typedef WPEmojiSettings 12 * @type {object} 13 * @property {?object} source 14 * @property {?string} source.concatemoji 15 * @property {?string} source.twemoji 16 * @property {?string} source.wpemoji 17 */ 18 19 const settings = /** @type {WPEmojiSettings} */ ( 20 JSON.parse( document.getElementById( 'wp-emoji-settings' ).textContent ) 21 ); 22 23 // For compatibility with other scripts that read from this global, in particular wp-includes/js/wp-emoji.js (source file: js/_enqueues/wp/emoji.js). 24 window._wpemojiSettings = settings; 25 26 /** 27 * Support tests. 28 * @typedef SupportTests 29 * @type {object} 30 * @property {?boolean} flag 31 * @property {?boolean} emoji 32 */ 33 34 const sessionStorageKey = 'wpEmojiSettingsSupports'; 35 const tests = [ 'flag', 'emoji' ]; 36 37 /** 38 * Checks whether the browser supports offloading to a Worker. 39 * 40 * @since 6.3.0 41 * 42 * @private 43 * 44 * @returns {boolean} 45 */ 46 function supportsWorkerOffloading() { 47 return ( 48 typeof Worker !== 'undefined' && 49 typeof OffscreenCanvas !== 'undefined' && 50 typeof URL !== 'undefined' && 51 URL.createObjectURL && 52 typeof Blob !== 'undefined' 53 ); 54 } 55 56 /** 57 * @typedef SessionSupportTests 58 * @type {object} 59 * @property {number} timestamp 60 * @property {SupportTests} supportTests 61 */ 62 63 /** 64 * Get support tests from session. 65 * 66 * @since 6.3.0 67 * 68 * @private 69 * 70 * @returns {?SupportTests} Support tests, or null if not set or older than 1 week. 71 */ 72 function getSessionSupportTests() { 73 try { 74 /** @type {SessionSupportTests} */ 75 const item = JSON.parse( 76 sessionStorage.getItem( sessionStorageKey ) 77 ); 78 if ( 79 typeof item === 'object' && 80 typeof item.timestamp === 'number' && 81 new Date().valueOf() < item.timestamp + 604800 && // Note: Number is a week in seconds. 82 typeof item.supportTests === 'object' 83 ) { 84 return item.supportTests; 85 } 86 } catch ( e ) {} 87 return null; 88 } 89 90 /** 91 * Persist the supports in session storage. 92 * 93 * @since 6.3.0 94 * 95 * @private 96 * 97 * @param {SupportTests} supportTests Support tests. 98 */ 99 function setSessionSupportTests( supportTests ) { 100 try { 101 /** @type {SessionSupportTests} */ 102 const item = { 103 supportTests: supportTests, 104 timestamp: new Date().valueOf() 105 }; 106 107 sessionStorage.setItem( 108 sessionStorageKey, 109 JSON.stringify( item ) 110 ); 111 } catch ( e ) {} 112 } 113 114 /** 115 * Checks if two sets of Emoji characters render the same visually. 116 * 117 * This is used to determine if the browser is rendering an emoji with multiple data points 118 * correctly. set1 is the emoji in the correct form, using a zero-width joiner. set2 is the emoji 119 * in the incorrect form, using a zero-width space. If the two sets render the same, then the browser 120 * does not support the emoji correctly. 121 * 122 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing 123 * scope. Everything must be passed by parameters. 124 * 125 * @since 4.9.0 126 * 127 * @private 128 * 129 * @param {CanvasRenderingContext2D} context 2D Context. 130 * @param {string} set1 Set of Emoji to test. 131 * @param {string} set2 Set of Emoji to test. 132 * 133 * @return {boolean} True if the two sets render the same. 134 */ 135 function emojiSetsRenderIdentically( context, set1, set2 ) { 136 // Cleanup from previous test. 137 context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); 138 context.fillText( set1, 0, 0 ); 139 const rendered1 = new Uint32Array( 140 context.getImageData( 141 0, 142 0, 143 context.canvas.width, 144 context.canvas.height 145 ).data 146 ); 147 148 // Cleanup from previous test. 149 context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); 150 context.fillText( set2, 0, 0 ); 151 const rendered2 = new Uint32Array( 152 context.getImageData( 153 0, 154 0, 155 context.canvas.width, 156 context.canvas.height 157 ).data 158 ); 159 160 return rendered1.every( ( rendered2Data, index ) => { 161 return rendered2Data === rendered2[ index ]; 162 } ); 163 } 164 165 /** 166 * Checks if the center point of a single emoji is empty. 167 * 168 * This is used to determine if the browser is rendering an emoji with a single data point 169 * correctly. The center point of an incorrectly rendered emoji will be empty. A correctly 170 * rendered emoji will have a non-zero value at the center point. 171 * 172 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing 173 * scope. Everything must be passed by parameters. 174 * 175 * @since 6.8.2 176 * 177 * @private 178 * 179 * @param {CanvasRenderingContext2D} context 2D Context. 180 * @param {string} emoji Emoji to test. 181 * 182 * @return {boolean} True if the center point is empty. 183 */ 184 function emojiRendersEmptyCenterPoint( context, emoji ) { 185 // Cleanup from previous test. 186 context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); 187 context.fillText( emoji, 0, 0 ); 188 189 // Test if the center point (16, 16) is empty (0,0,0,0). 190 const centerPoint = context.getImageData(16, 16, 1, 1); 191 for ( let i = 0; i < centerPoint.data.length; i++ ) { 192 if ( centerPoint.data[ i ] !== 0 ) { 193 // Stop checking the moment it's known not to be empty. 194 return false; 195 } 196 } 197 198 return true; 199 } 200 201 /** 202 * Determines if the browser properly renders Emoji that Twemoji can supplement. 203 * 204 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing 205 * scope. Everything must be passed by parameters. 206 * 207 * @since 4.2.0 208 * 209 * @private 210 * 211 * @param {CanvasRenderingContext2D} context 2D Context. 212 * @param {string} type Whether to test for support of "flag" or "emoji". 213 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification. 214 * @param {Function} emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification. 215 * 216 * @return {boolean} True if the browser can render emoji, false if it cannot. 217 */ 218 function browserSupportsEmoji( context, type, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) { 219 let isIdentical; 220 221 switch ( type ) { 222 case 'flag': 223 /* 224 * Test for Transgender flag compatibility. Added in Unicode 13. 225 * 226 * To test for support, we try to render it, and compare the rendering to how it would look if 227 * the browser doesn't render it correctly (white flag emoji + transgender symbol). 228 */ 229 isIdentical = emojiSetsRenderIdentically( 230 context, 231 '\uD83C\uDFF3\uFE0F\u200D\u26A7\uFE0F', // as a zero-width joiner sequence 232 '\uD83C\uDFF3\uFE0F\u200B\u26A7\uFE0F' // separated by a zero-width space 233 ); 234 235 if ( isIdentical ) { 236 return false; 237 } 238 239 /* 240 * Test for Sark flag compatibility. This is the least supported of the letter locale flags, 241 * so gives us an easy test for full support. 242 * 243 * To test for support, we try to render it, and compare the rendering to how it would look if 244 * the browser doesn't render it correctly ([C] + [Q]). 245 */ 246 isIdentical = emojiSetsRenderIdentically( 247 context, 248 '\uD83C\uDDE8\uD83C\uDDF6', // as the sequence of two code points 249 '\uD83C\uDDE8\u200B\uD83C\uDDF6' // as the two code points separated by a zero-width space 250 ); 251 252 if ( isIdentical ) { 253 return false; 254 } 255 256 /* 257 * Test for English flag compatibility. England is a country in the United Kingdom, it 258 * does not have a two letter locale code but rather a five letter sub-division code. 259 * 260 * To test for support, we try to render it, and compare the rendering to how it would look if 261 * the browser doesn't render it correctly (black flag emoji + [G] + [B] + [E] + [N] + [G]). 262 */ 263 isIdentical = emojiSetsRenderIdentically( 264 context, 265 // as the flag sequence 266 '\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F', 267 // with each code point separated by a zero-width space 268 '\uD83C\uDFF4\u200B\uDB40\uDC67\u200B\uDB40\uDC62\u200B\uDB40\uDC65\u200B\uDB40\uDC6E\u200B\uDB40\uDC67\u200B\uDB40\uDC7F' 269 ); 270 271 return ! isIdentical; 272 case 'emoji': 273 /* 274 * Does Emoji 16.0 cause the browser to go splat? 275 * 276 * To test for Emoji 16.0 support, try to render a new emoji: Splatter. 277 * 278 * The splatter emoji is a single code point emoji. Testing for browser support 279 * required testing the center point of the emoji to see if it is empty. 280 * 281 * 0xD83E 0xDEDF (\uD83E\uDEDF) == 🫟 Splatter. 282 * 283 * When updating this test, please ensure that the emoji is either a single code point 284 * or switch to using the emojiSetsRenderIdentically function and testing with a zero-width 285 * joiner vs a zero-width space. 286 */ 287 const notSupported = emojiRendersEmptyCenterPoint( context, '\uD83E\uDEDF' ); 288 return ! notSupported; 289 } 290 291 return false; 292 } 293 294 /** 295 * Checks emoji support tests. 296 * 297 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing 298 * scope. Everything must be passed by parameters. 299 * 300 * @since 6.3.0 301 * 302 * @private 303 * 304 * @param {string[]} tests Tests. 305 * @param {Function} browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification. 306 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification. 307 * @param {Function} emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification. 308 * 309 * @return {SupportTests} Support tests. 310 */ 311 function testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) { 312 let canvas; 313 if ( 314 typeof WorkerGlobalScope !== 'undefined' && 315 self instanceof WorkerGlobalScope 316 ) { 317 canvas = new OffscreenCanvas( 300, 150 ); // Dimensions are default for HTMLCanvasElement. 318 } else { 319 canvas = document.createElement( 'canvas' ); 320 } 321 322 const context = canvas.getContext( '2d', { willReadFrequently: true } ); 323 324 /* 325 * Chrome on OS X added native emoji rendering in M41. Unfortunately, 326 * it doesn't work when the font is bolder than 500 weight. So, we 327 * check for bold rendering support to avoid invisible emoji in Chrome. 328 */ 329 context.textBaseline = 'top'; 330 context.font = '600 32px Arial'; 331 332 const supports = {}; 333 tests.forEach( ( test ) => { 334 supports[ test ] = browserSupportsEmoji( context, test, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ); 335 } ); 336 return supports; 337 } 338 339 /** 340 * Adds a script to the head of the document. 341 * 342 * @ignore 343 * 344 * @since 4.2.0 345 * 346 * @param {string} src The url where the script is located. 347 * 348 * @return {void} 349 */ 350 function addScript( src ) { 351 const script = document.createElement( 'script' ); 352 script.src = src; 353 script.defer = true; 354 document.head.appendChild( script ); 355 } 356 357 settings.supports = { 358 everything: true, 359 everythingExceptFlag: true 360 }; 361 362 // Obtain the emoji support from the browser, asynchronously when possible. 363 new Promise( ( resolve ) => { 364 let supportTests = getSessionSupportTests(); 365 if ( supportTests ) { 366 resolve( supportTests ); 367 return; 368 } 369 370 if ( supportsWorkerOffloading() ) { 371 try { 372 // Note that the functions are being passed as arguments due to minification. 373 const workerScript = 374 'postMessage(' + 375 testEmojiSupports.toString() + 376 '(' + 377 [ 378 JSON.stringify( tests ), 379 browserSupportsEmoji.toString(), 380 emojiSetsRenderIdentically.toString(), 381 emojiRendersEmptyCenterPoint.toString() 382 ].join( ',' ) + 383 '));'; 384 const blob = new Blob( [ workerScript ], { 385 type: 'text/javascript' 386 } ); 387 const worker = new Worker( URL.createObjectURL( blob ), { name: 'wpTestEmojiSupports' } ); 388 worker.onmessage = ( event ) => { 389 supportTests = event.data; 390 setSessionSupportTests( supportTests ); 391 worker.terminate(); 392 resolve( supportTests ); 393 }; 394 return; 395 } catch ( e ) {} 396 } 397 398 supportTests = testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ); 399 setSessionSupportTests( supportTests ); 400 resolve( supportTests ); 401 } ) 402 // Once the browser emoji support has been obtained from the session, finalize the settings. 403 .then( ( supportTests ) => { 404 /* 405 * Tests the browser support for flag emojis and other emojis, and adjusts the 406 * support settings accordingly. 407 */ 408 for ( const test in supportTests ) { 409 settings.supports[ test ] = supportTests[ test ]; 410 411 settings.supports.everything = 412 settings.supports.everything && settings.supports[ test ]; 413 414 if ( 'flag' !== test ) { 415 settings.supports.everythingExceptFlag = 416 settings.supports.everythingExceptFlag && 417 settings.supports[ test ]; 418 } 419 } 420 421 settings.supports.everythingExceptFlag = 422 settings.supports.everythingExceptFlag && 423 ! settings.supports.flag; 424 425 // When the browser can not render everything we need to load a polyfill. 426 if ( ! settings.supports.everything ) { 427 const src = settings.source || {}; 428 429 if ( src.concatemoji ) { 430 addScript( src.concatemoji ); 431 } else if ( src.wpemoji && src.twemoji ) { 432 addScript( src.twemoji ); 433 addScript( src.wpemoji ); 434 } 435 } 436 } );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Oct 10 08:20:03 2025 | Cross-referenced by PHPXref |