[ 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 function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
 121       * scope. Everything must be passed by parameters.
 122       *
 123       * @since 4.9.0
 124       *
 125       * @private
 126       *
 127       * @param {CanvasRenderingContext2D} context 2D Context.
 128       * @param {string} set1 Set of Emoji to test.
 129       * @param {string} set2 Set of Emoji to test.
 130       *
 131       * @return {boolean} True if the two sets render the same.
 132       */
 133  	function emojiSetsRenderIdentically( context, set1, set2 ) {
 134          // Cleanup from previous test.
 135          context.clearRect( 0, 0, context.canvas.width, context.canvas.height );
 136          context.fillText( set1, 0, 0 );
 137          var rendered1 = new Uint32Array(
 138              context.getImageData(
 139                  0,
 140                  0,
 141                  context.canvas.width,
 142                  context.canvas.height
 143              ).data
 144          );
 145  
 146          // Cleanup from previous test.
 147          context.clearRect( 0, 0, context.canvas.width, context.canvas.height );
 148          context.fillText( set2, 0, 0 );
 149          var rendered2 = new Uint32Array(
 150              context.getImageData(
 151                  0,
 152                  0,
 153                  context.canvas.width,
 154                  context.canvas.height
 155              ).data
 156          );
 157  
 158          return rendered1.every( function ( rendered2Data, index ) {
 159              return rendered2Data === rendered2[ index ];
 160          } );
 161      }
 162  
 163      /**
 164       * Determines if the browser properly renders Emoji that Twemoji can supplement.
 165       *
 166       * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
 167       * scope. Everything must be passed by parameters.
 168       *
 169       * @since 4.2.0
 170       *
 171       * @private
 172       *
 173       * @param {CanvasRenderingContext2D} context 2D Context.
 174       * @param {string} type Whether to test for support of "flag" or "emoji".
 175       * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
 176       *
 177       * @return {boolean} True if the browser can render emoji, false if it cannot.
 178       */
 179  	function browserSupportsEmoji( context, type, emojiSetsRenderIdentically ) {
 180          var isIdentical;
 181  
 182          switch ( type ) {
 183              case 'flag':
 184                  /*
 185                   * Test for Transgender flag compatibility. Added in Unicode 13.
 186                   *
 187                   * To test for support, we try to render it, and compare the rendering to how it would look if
 188                   * the browser doesn't render it correctly (white flag emoji + transgender symbol).
 189                   */
 190                  isIdentical = emojiSetsRenderIdentically(
 191                      context,
 192                      '\uD83C\uDFF3\uFE0F\u200D\u26A7\uFE0F', // as a zero-width joiner sequence
 193                      '\uD83C\uDFF3\uFE0F\u200B\u26A7\uFE0F' // separated by a zero-width space
 194                  );
 195  
 196                  if ( isIdentical ) {
 197                      return false;
 198                  }
 199  
 200                  /*
 201                   * Test for UN flag compatibility. This is the least supported of the letter locale flags,
 202                   * so gives us an easy test for full support.
 203                   *
 204                   * To test for support, we try to render it, and compare the rendering to how it would look if
 205                   * the browser doesn't render it correctly ([U] + [N]).
 206                   */
 207                  isIdentical = emojiSetsRenderIdentically(
 208                      context,
 209                      '\uD83C\uDDFA\uD83C\uDDF3', // as the sequence of two code points
 210                      '\uD83C\uDDFA\u200B\uD83C\uDDF3' // as the two code points separated by a zero-width space
 211                  );
 212  
 213                  if ( isIdentical ) {
 214                      return false;
 215                  }
 216  
 217                  /*
 218                   * Test for English flag compatibility. England is a country in the United Kingdom, it
 219                   * does not have a two letter locale code but rather a five letter sub-division code.
 220                   *
 221                   * To test for support, we try to render it, and compare the rendering to how it would look if
 222                   * the browser doesn't render it correctly (black flag emoji + [G] + [B] + [E] + [N] + [G]).
 223                   */
 224                  isIdentical = emojiSetsRenderIdentically(
 225                      context,
 226                      // as the flag sequence
 227                      '\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F',
 228                      // with each code point separated by a zero-width space
 229                      '\uD83C\uDFF4\u200B\uDB40\uDC67\u200B\uDB40\uDC62\u200B\uDB40\uDC65\u200B\uDB40\uDC6E\u200B\uDB40\uDC67\u200B\uDB40\uDC7F'
 230                  );
 231  
 232                  return ! isIdentical;
 233              case 'emoji':
 234                  /*
 235                   * Four and twenty blackbirds baked in a pie.
 236                   *
 237                   * To test for Emoji 15.0 support, try to render a new emoji: Blackbird.
 238                   *
 239                   * The Blackbird is a ZWJ sequence combining 🐦 Bird and ⬛ large black square.,
 240                   *
 241                   * 0x1F426 (\uD83D\uDC26) == Bird
 242                   * 0x200D == Zero-Width Joiner (ZWJ) that links the code points for the new emoji or
 243                   * 0x200B == Zero-Width Space (ZWS) that is rendered for clients not supporting the new emoji.
 244                   * 0x2B1B == Large Black Square
 245                   *
 246                   * When updating this test for future Emoji releases, ensure that individual emoji that make up the
 247                   * sequence come from older emoji standards.
 248                   */
 249                  isIdentical = emojiSetsRenderIdentically(
 250                      context,
 251                      '\uD83D\uDC26\u200D\u2B1B', // as the zero-width joiner sequence
 252                      '\uD83D\uDC26\u200B\u2B1B' // separated by a zero-width space
 253                  );
 254  
 255                  return ! isIdentical;
 256          }
 257  
 258          return false;
 259      }
 260  
 261      /**
 262       * Checks emoji support tests.
 263       *
 264       * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
 265       * scope. Everything must be passed by parameters.
 266       *
 267       * @since 6.3.0
 268       *
 269       * @private
 270       *
 271       * @param {string[]} tests Tests.
 272       * @param {Function} browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification.
 273       * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
 274       *
 275       * @return {SupportTests} Support tests.
 276       */
 277  	function testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically ) {
 278          var canvas;
 279          if (
 280              typeof WorkerGlobalScope !== 'undefined' &&
 281              self instanceof WorkerGlobalScope
 282          ) {
 283              canvas = new OffscreenCanvas( 300, 150 ); // Dimensions are default for HTMLCanvasElement.
 284          } else {
 285              canvas = document.createElement( 'canvas' );
 286          }
 287  
 288          var context = canvas.getContext( '2d', { willReadFrequently: true } );
 289  
 290          /*
 291           * Chrome on OS X added native emoji rendering in M41. Unfortunately,
 292           * it doesn't work when the font is bolder than 500 weight. So, we
 293           * check for bold rendering support to avoid invisible emoji in Chrome.
 294           */
 295          context.textBaseline = 'top';
 296          context.font = '600 32px Arial';
 297  
 298          var supports = {};
 299          tests.forEach( function ( test ) {
 300              supports[ test ] = browserSupportsEmoji( context, test, emojiSetsRenderIdentically );
 301          } );
 302          return supports;
 303      }
 304  
 305      /**
 306       * Adds a script to the head of the document.
 307       *
 308       * @ignore
 309       *
 310       * @since 4.2.0
 311       *
 312       * @param {string} src The url where the script is located.
 313       *
 314       * @return {void}
 315       */
 316  	function addScript( src ) {
 317          var script = document.createElement( 'script' );
 318          script.src = src;
 319          script.defer = true;
 320          document.head.appendChild( script );
 321      }
 322  
 323      settings.supports = {
 324          everything: true,
 325          everythingExceptFlag: true
 326      };
 327  
 328      // Create a promise for DOMContentLoaded since the worker logic may finish after the event has fired.
 329      var domReadyPromise = new Promise( function ( resolve ) {
 330          document.addEventListener( 'DOMContentLoaded', resolve, {
 331              once: true
 332          } );
 333      } );
 334  
 335      // Obtain the emoji support from the browser, asynchronously when possible.
 336      new Promise( function ( resolve ) {
 337          var supportTests = getSessionSupportTests();
 338          if ( supportTests ) {
 339              resolve( supportTests );
 340              return;
 341          }
 342  
 343          if ( supportsWorkerOffloading() ) {
 344              try {
 345                  // Note that the functions are being passed as arguments due to minification.
 346                  var workerScript =
 347                      'postMessage(' +
 348                      testEmojiSupports.toString() +
 349                      '(' +
 350                      [
 351                          JSON.stringify( tests ),
 352                          browserSupportsEmoji.toString(),
 353                          emojiSetsRenderIdentically.toString()
 354                      ].join( ',' ) +
 355                      '));';
 356                  var blob = new Blob( [ workerScript ], {
 357                      type: 'text/javascript'
 358                  } );
 359                  var worker = new Worker( URL.createObjectURL( blob ), { name: 'wpTestEmojiSupports' } );
 360                  worker.onmessage = function ( event ) {
 361                      supportTests = event.data;
 362                      setSessionSupportTests( supportTests );
 363                      worker.terminate();
 364                      resolve( supportTests );
 365                  };
 366                  return;
 367              } catch ( e ) {}
 368          }
 369  
 370          supportTests = testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically );
 371          setSessionSupportTests( supportTests );
 372          resolve( supportTests );
 373      } )
 374          // Once the browser emoji support has been obtained from the session, finalize the settings.
 375          .then( function ( supportTests ) {
 376              /*
 377               * Tests the browser support for flag emojis and other emojis, and adjusts the
 378               * support settings accordingly.
 379               */
 380              for ( var test in supportTests ) {
 381                  settings.supports[ test ] = supportTests[ test ];
 382  
 383                  settings.supports.everything =
 384                      settings.supports.everything && settings.supports[ test ];
 385  
 386                  if ( 'flag' !== test ) {
 387                      settings.supports.everythingExceptFlag =
 388                          settings.supports.everythingExceptFlag &&
 389                          settings.supports[ test ];
 390                  }
 391              }
 392  
 393              settings.supports.everythingExceptFlag =
 394                  settings.supports.everythingExceptFlag &&
 395                  ! settings.supports.flag;
 396  
 397              // Sets DOMReady to false and assigns a ready function to settings.
 398              settings.DOMReady = false;
 399              settings.readyCallback = function () {
 400                  settings.DOMReady = true;
 401              };
 402          } )
 403          .then( function () {
 404              return domReadyPromise;
 405          } )
 406          .then( function () {
 407              // When the browser can not render everything we need to load a polyfill.
 408              if ( ! settings.supports.everything ) {
 409                  settings.readyCallback();
 410  
 411                  var src = settings.source || {};
 412  
 413                  if ( src.concatemoji ) {
 414                      addScript( src.concatemoji );
 415                  } else if ( src.wpemoji && src.twemoji ) {
 416                      addScript( src.twemoji );
 417                      addScript( src.wpemoji );
 418                  }
 419              }
 420          } );
 421  } )( window, document, window._wpemojiSettings );


Generated : Thu Apr 25 08:20:02 2024 Cross-referenced by PHPXref