<!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>