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