[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/js/ -> wp-emoji-loader.js (source)

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


Generated : Wed May 14 08:20:01 2025 Cross-referenced by PHPXref