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