<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[56074] trunk: Emoji: Optimize emoji loader with `sessionStorage`, `willReadFrequently`, and `OffscreenCanvas`.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/56074">56074</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/56074","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>westonruter</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-06-27 17:22:59 +0000 (Tue, 27 Jun 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Emoji: Optimize emoji loader with `sessionStorage`, `willReadFrequently`, and `OffscreenCanvas`.

* Use `sessionStorage` to remember the previous results of calls to `browserSupportsEmoji()` for 1 week.
* Optimize reading from canvas by supplying the `willReadFrequently` option for the 2D context.
* When `OffscreenCanvas` is available, offload `browserSupportsEmoji()` to a web worker to prevent blocking the main thread. This is of primary benefit to Safari which does not yet support `willReadFrequently`.
* Remove obsolete support for IE11 since promises are now utilized. Nevertheless, ES3 syntax is maintained and IE11 will simply short-circuit.

Props westonruter, dmsnell, peterwilsoncc, valterlorran, flixos90, spacedmonkey, joemcgill.
Fixes <a href="https://core.trac.wordpress.org/ticket/58472">#58472</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkjshintrc">trunk/.jshintrc</a></li>
<li><a href="#trunksrcjs_enqueueslibemojiloaderjs">trunk/src/js/_enqueues/lib/emoji-loader.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkjshintrc"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/.jshintrc</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/.jshintrc   2023-06-27 17:10:28 UTC (rev 56073)
+++ trunk/.jshintrc     2023-06-27 17:22:59 UTC (rev 56074)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -24,6 +24,10 @@
</span><span class="cx" style="display: block; padding: 0 10px">                "wp": false,
</span><span class="cx" style="display: block; padding: 0 10px">                "export": false,
</span><span class="cx" style="display: block; padding: 0 10px">                "module": false,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                "require": false
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         "require": false,
+               "WorkerGlobalScope": false,
+               "self": false,
+               "OffscreenCanvas": false,
+               "Promise": false
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunksrcjs_enqueueslibemojiloaderjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/js/_enqueues/lib/emoji-loader.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/js/_enqueues/lib/emoji-loader.js        2023-06-27 17:10:28 UTC (rev 56073)
+++ trunk/src/js/_enqueues/lib/emoji-loader.js  2023-06-27 17:22:59 UTC (rev 56074)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2,65 +2,189 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @output wp-includes/js/wp-emoji-loader.js
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-( function( window, document, settings ) {
-       var src, ready, ii, tests;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * Emoji Settings as exported in PHP via _print_emoji_detection_script().
+ * @typedef WPEmojiSettings
+ * @type {object}
+ * @property {?object} source
+ * @property {?string} source.concatemoji
+ * @property {?string} source.twemoji
+ * @property {?string} source.wpemoji
+ * @property {?boolean} DOMReady
+ * @property {?Function} readyCallback
+ */
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // Create a canvas element for testing native browser support of emoji.
-       var canvas = document.createElement( 'canvas' );
-       var context = canvas.getContext && canvas.getContext( '2d' );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * Support tests.
+ * @typedef SupportTests
+ * @type {object}
+ * @property {?boolean} flag
+ * @property {?boolean} emoji
+ */
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * IIFE to detect emoji support and load Twemoji if needed.
+ *
+ * @param {Window} window
+ * @param {Document} document
+ * @param {WPEmojiSettings} settings
+ */
+( function wpEmojiLoader( window, document, settings ) {
+       if ( typeof Promise === 'undefined' ) {
+               return;
+       }
+
+       var sessionStorageKey = 'wpEmojiSettingsSupports';
+       var tests = [ 'flag', 'emoji' ];
+
</ins><span class="cx" style="display: block; padding: 0 10px">         /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Checks whether the browser supports offloading to a Worker.
+        *
+        * @since 6.3.0
+        *
+        * @private
+        *
+        * @returns {boolean}
+        */
+       function supportsWorkerOffloading() {
+               return (
+                       typeof Worker !== 'undefined' &&
+                       typeof OffscreenCanvas !== 'undefined' &&
+                       typeof URL !== 'undefined' &&
+                       URL.createObjectURL &&
+                       typeof Blob !== 'undefined'
+               );
+       }
+
+       /**
+        * @typedef SessionSupportTests
+        * @type {object}
+        * @property {number} timestamp
+        * @property {SupportTests} supportTests
+        */
+
+       /**
+        * Get support tests from session.
+        *
+        * @since 6.3.0
+        *
+        * @private
+        *
+        * @returns {?SupportTests} Support tests, or null if not set or older than 1 week.
+        */
+       function getSessionSupportTests() {
+               if (
+                       typeof sessionStorage !== 'undefined' &&
+                       sessionStorageKey in sessionStorage
+               ) {
+                       try {
+                               /** @type {SessionSupportTests} */
+                               var item = JSON.parse(
+                                       sessionStorage.getItem( sessionStorageKey )
+                               );
+                               if (
+                                       typeof item === 'object' &&
+                                       typeof item.timestamp === 'number' &&
+                                       new Date().valueOf() < item.timestamp + 604800 && // Note: Number is a week in seconds.
+                                       typeof item.supportTests === 'object'
+                               ) {
+                                       return item.supportTests;
+                               }
+                       } catch ( e ) {}
+               }
+               return null;
+       }
+
+       /**
+        * Persist the supports in session storage.
+        *
+        * @since 6.3.0
+        *
+        * @private
+        *
+        * @param {SupportTests} supportTests Support tests.
+        */
+       function setSessionSupportTests( supportTests ) {
+               if ( typeof sessionStorage !== 'undefined' ) {
+                       try {
+                               /** @type {SessionSupportTests} */
+                               var item = {
+                                       supportTests: supportTests,
+                                       timestamp: new Date().valueOf()
+                               };
+
+                               sessionStorage.setItem(
+                                       sessionStorageKey,
+                                       JSON.stringify( item )
+                               );
+                       } catch ( e ) {}
+               }
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Checks if two sets of Emoji characters render the same visually.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
+        * scope. Everything must be passed by parameters.
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @since 4.9.0
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @private
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @param {CanvasRenderingContext2D} context 2D Context.
</ins><span class="cx" style="display: block; padding: 0 10px">          * @param {string} set1 Set of Emoji to test.
</span><span class="cx" style="display: block; padding: 0 10px">         * @param {string} set2 Set of Emoji to test.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return {boolean} True if the two sets render the same.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        function emojiSetsRenderIdentically( set1, set2 ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ function emojiSetsRenderIdentically( context, set1, set2 ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 // Cleanup from previous test.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                context.clearRect( 0, 0, canvas.width, canvas.height );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         context.clearRect( 0, 0, context.canvas.width, context.canvas.height );
</ins><span class="cx" style="display: block; padding: 0 10px">                 context.fillText( set1, 0, 0 );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                var rendered1 = canvas.toDataURL();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         var rendered1 = new Uint32Array(
+                       context.getImageData(
+                               0,
+                               0,
+                               context.canvas.width,
+                               context.canvas.height
+                       ).data
+               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                // Cleanup from previous test.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                context.clearRect( 0, 0, canvas.width, canvas.height );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         context.clearRect( 0, 0, context.canvas.width, context.canvas.height );
</ins><span class="cx" style="display: block; padding: 0 10px">                 context.fillText( set2, 0, 0 );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                var rendered2 = canvas.toDataURL();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         var rendered2 = new Uint32Array(
+                       context.getImageData(
+                               0,
+                               0,
+                               context.canvas.width,
+                               context.canvas.height
+                       ).data
+               );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return rendered1 === rendered2;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return rendered1.every( function ( rendered2Data, index ) {
+                       return rendered2Data === rendered2[ index ];
+               } );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="cx" style="display: block; padding: 0 10px">         * Determines if the browser properly renders Emoji that Twemoji can supplement.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
+        * scope. Everything must be passed by parameters.
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @since 4.2.0
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @private
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * @param {CanvasRenderingContext2D} context 2D Context.
</ins><span class="cx" style="display: block; padding: 0 10px">          * @param {string} type Whether to test for support of "flag" or "emoji".
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return {boolean} True if the browser can render emoji, false if it cannot.
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        function browserSupportsEmoji( type ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ function browserSupportsEmoji( context, type ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 var isIdentical;
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! context || ! context.fillText ) {
-                       return false;
-               }
-
-               /*
-                * Chrome on OS X added native emoji rendering in M41. Unfortunately,
-                * it doesn't work when the font is bolder than 500 weight. So, we
-                * check for bold rendering support to avoid invisible emoji in Chrome.
-                */
-               context.textBaseline = 'top';
-               context.font = '600 32px Arial';
-
</del><span class="cx" style="display: block; padding: 0 10px">                 switch ( type ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        case 'flag':
</span><span class="cx" style="display: block; padding: 0 10px">                                /*
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -70,8 +194,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                 * the browser doesn't render it correctly (white flag emoji + transgender symbol).
</span><span class="cx" style="display: block; padding: 0 10px">                                 */
</span><span class="cx" style="display: block; padding: 0 10px">                                isIdentical = emojiSetsRenderIdentically(
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        context,
</ins><span class="cx" style="display: block; padding: 0 10px">                                         '\uD83C\uDFF3\uFE0F\u200D\u26A7\uFE0F', // as a zero-width joiner sequence
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        '\uD83C\uDFF3\uFE0F\u200B\u26A7\uFE0F'  // separated by a zero-width space
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 '\uD83C\uDFF3\uFE0F\u200B\u26A7\uFE0F' // separated by a zero-width space
</ins><span class="cx" style="display: block; padding: 0 10px">                                 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( isIdentical ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -86,8 +211,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                 * the browser doesn't render it correctly ([U] + [N]).
</span><span class="cx" style="display: block; padding: 0 10px">                                 */
</span><span class="cx" style="display: block; padding: 0 10px">                                isIdentical = emojiSetsRenderIdentically(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        '\uD83C\uDDFA\uD83C\uDDF3',       // as the sequence of two code points
-                                       '\uD83C\uDDFA\u200B\uD83C\uDDF3'  // as the two code points separated by a zero-width space
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 context,
+                                       '\uD83C\uDDFA\uD83C\uDDF3', // as the sequence of two code points
+                                       '\uD83C\uDDFA\u200B\uD83C\uDDF3' // as the two code points separated by a zero-width space
</ins><span class="cx" style="display: block; padding: 0 10px">                                 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                if ( isIdentical ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -102,6 +228,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                 * the browser doesn't render it correctly (black flag emoji + [G] + [B] + [E] + [N] + [G]).
</span><span class="cx" style="display: block; padding: 0 10px">                                 */
</span><span class="cx" style="display: block; padding: 0 10px">                                isIdentical = emojiSetsRenderIdentically(
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        context,
</ins><span class="cx" style="display: block; padding: 0 10px">                                         // as the flag sequence
</span><span class="cx" style="display: block; padding: 0 10px">                                        '\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F',
</span><span class="cx" style="display: block; padding: 0 10px">                                        // with each code point separated by a zero-width space
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -129,8 +256,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                 * sequence come from older emoji standards.
</span><span class="cx" style="display: block; padding: 0 10px">                                 */
</span><span class="cx" style="display: block; padding: 0 10px">                                isIdentical = emojiSetsRenderIdentically(
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        context,
</ins><span class="cx" style="display: block; padding: 0 10px">                                         '\uD83E\uDEF1\uD83C\uDFFB\u200D\uD83E\uDEF2\uD83C\uDFFF', // as the zero-width joiner sequence
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        '\uD83E\uDEF1\uD83C\uDFFB\u200B\uD83E\uDEF2\uD83C\uDFFF'  // separated by a zero-width space
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 '\uD83E\uDEF1\uD83C\uDFFB\u200B\uD83E\uDEF2\uD83C\uDFFF' // separated by a zero-width space
</ins><span class="cx" style="display: block; padding: 0 10px">                                 );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                                return ! isIdentical;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -140,6 +268,48 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         * Checks emoji support tests.
+        *
+        * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
+        * scope. Everything must be passed by parameters.
+        *
+        * @since 6.3.0
+        *
+        * @private
+        *
+        * @param {string[]} tests Tests.
+        *
+        * @return {SupportTests} Support tests.
+        */
+       function testEmojiSupports( tests ) {
+               var canvas;
+               if (
+                       typeof WorkerGlobalScope !== 'undefined' &&
+                       self instanceof WorkerGlobalScope
+               ) {
+                       canvas = new OffscreenCanvas( 300, 150 ); // Dimensions are default for HTMLCanvasElement.
+               } else {
+                       canvas = document.createElement( 'canvas' );
+               }
+
+               var context = canvas.getContext( '2d', { willReadFrequently: true } );
+
+               /*
+                * Chrome on OS X added native emoji rendering in M41. Unfortunately,
+                * it doesn't work when the font is bolder than 500 weight. So, we
+                * check for bold rendering support to avoid invisible emoji in Chrome.
+                */
+               context.textBaseline = 'top';
+               context.font = '600 32px Arial';
+
+               var supports = {};
+               tests.forEach( function ( test ) {
+                       supports[ test ] = browserSupportsEmoji( context, test );
+               } );
+               return supports;
+       }
+
+       /**
</ins><span class="cx" style="display: block; padding: 0 10px">          * Adds a script to the head of the document.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @ignore
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -146,75 +316,115 @@
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @since 4.2.0
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * @param {Object} src The url where the script is located.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+  * @param {string} src The url where the script is located.
+        *
</ins><span class="cx" style="display: block; padding: 0 10px">          * @return {void}
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        function addScript( src ) {
</span><span class="cx" style="display: block; padding: 0 10px">                var script = document.createElement( 'script' );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-
</del><span class="cx" style="display: block; padding: 0 10px">                 script.src = src;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                script.defer = script.type = 'text/javascript';
-               document.getElementsByTagName( 'head' )[0].appendChild( script );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         script.defer = true;
+               document.head.appendChild( script );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        tests = Array( 'flag', 'emoji' );
-
</del><span class="cx" style="display: block; padding: 0 10px">         settings.supports = {
</span><span class="cx" style="display: block; padding: 0 10px">                everything: true,
</span><span class="cx" style="display: block; padding: 0 10px">                everythingExceptFlag: true
</span><span class="cx" style="display: block; padding: 0 10px">        };
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /*
-        * Tests the browser support for flag emojis and other emojis, and adjusts the
-        * support settings accordingly.
-        */
-       for( ii = 0; ii < tests.length; ii++ ) {
-               settings.supports[ tests[ ii ] ] = browserSupportsEmoji( tests[ ii ] );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Create a promise for DOMContentLoaded since the worker logic may finish after the event has fired.
+       var domReadyPromise = new Promise( function ( resolve ) {
+               document.addEventListener( 'DOMContentLoaded', resolve, {
+                       once: true
+               } );
+       } );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                settings.supports.everything = settings.supports.everything && settings.supports[ tests[ ii ] ];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Obtain the emoji support from the browser, asynchronously when possible.
+       new Promise( function ( resolve ) {
+               var supportTests = getSessionSupportTests();
+               if ( supportTests ) {
+                       resolve( supportTests );
+                       return;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( 'flag' !== tests[ ii ] ) {
-                       settings.supports.everythingExceptFlag = settings.supports.everythingExceptFlag && settings.supports[ tests[ ii ] ];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( supportsWorkerOffloading() ) {
+                       try {
+                               /*
+                                * Note that this string contains the real source code for the
+                                * copied functions, _not_ a string representation of them. This
+                                * is because it's not possible to transfer a Function across
+                                * threads. The lack of quotes is intentional. The function names
+                                * are copied to variable names since minification will munge the
+                                * function names, thus breaking the ability for the functions to
+                                * refer to each other.
+                                */
+                               var workerScript =
+                                       'var emojiSetsRenderIdentically = ' + emojiSetsRenderIdentically + ';' +
+                                       'var browserSupportsEmoji = ' + browserSupportsEmoji + ';' +
+                                       'var testEmojiSupports = ' + testEmojiSupports + ';' +
+                                       'postMessage(testEmojiSupports(' + JSON.stringify(tests) + '));';
+                               var blob = new Blob( [ workerScript ], {
+                                       type: 'text/javascript'
+                               } );
+                               var worker = new Worker( URL.createObjectURL( blob ) );
+                               worker.onmessage = function ( event ) {
+                                       supportTests = event.data;
+                                       setSessionSupportTests( supportTests );
+                                       resolve( supportTests );
+                               };
+                               return;
+                       } catch ( e ) {}
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        }
</del><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        settings.supports.everythingExceptFlag = settings.supports.everythingExceptFlag && ! settings.supports.flag;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         supportTests = testEmojiSupports( tests );
+               setSessionSupportTests( supportTests );
+               resolve( supportTests );
+       } )
+               // Once the browser emoji support has been obtained from the session, finalize the settings.
+               .then( function ( supportTests ) {
+                       /*
+                        * Tests the browser support for flag emojis and other emojis, and adjusts the
+                        * support settings accordingly.
+                        */
+                       for ( var test in supportTests ) {
+                               settings.supports[ test ] = supportTests[ test ];
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // Sets DOMReady to false and assigns a ready function to settings.
-       settings.DOMReady = false;
-       settings.readyCallback = function() {
-               settings.DOMReady = true;
-       };
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         settings.supports.everything =
+                                       settings.supports.everything && settings.supports[ test ];
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // When the browser can not render everything we need to load a polyfill.
-       if ( ! settings.supports.everything ) {
-               ready = function() {
-                       settings.readyCallback();
-               };
-
-               /*
-                * Cross-browser version of adding a dom ready event.
-                */
-               if ( document.addEventListener ) {
-                       document.addEventListener( 'DOMContentLoaded', ready, false );
-                       window.addEventListener( 'load', ready, false );
-               } else {
-                       window.attachEvent( 'onload', ready );
-                       document.attachEvent( 'onreadystatechange', function() {
-                               if ( 'complete' === document.readyState ) {
-                                       settings.readyCallback();
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         if ( 'flag' !== test ) {
+                                       settings.supports.everythingExceptFlag =
+                                               settings.supports.everythingExceptFlag &&
+                                               settings.supports[ test ];
</ins><span class="cx" style="display: block; padding: 0 10px">                                 }
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        } );
-               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                src = settings.source || {};
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 settings.supports.everythingExceptFlag =
+                               settings.supports.everythingExceptFlag &&
+                               ! settings.supports.flag;
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( src.concatemoji ) {
-                       addScript( src.concatemoji );
-               } else if ( src.wpemoji && src.twemoji ) {
-                       addScript( src.twemoji );
-                       addScript( src.wpemoji );
-               }
-       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Sets DOMReady to false and assigns a ready function to settings.
+                       settings.DOMReady = false;
+                       settings.readyCallback = function () {
+                               settings.DOMReady = true;
+                       };
+               } )
+               .then( function () {
+                       return domReadyPromise;
+               } )
+               .then( function () {
+                       // When the browser can not render everything we need to load a polyfill.
+                       if ( ! settings.supports.everything ) {
+                               settings.readyCallback();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                var src = settings.source || {};
+
+                               if ( src.concatemoji ) {
+                                       addScript( src.concatemoji );
+                               } else if ( src.wpemoji && src.twemoji ) {
+                                       addScript( src.twemoji );
+                                       addScript( src.wpemoji );
+                               }
+                       }
+               } );
</ins><span class="cx" style="display: block; padding: 0 10px"> } )( window, document, window._wpemojiSettings );
</span></span></pre>
</div>
</div>

</body>
</html>