44
55// Note: This is loaded as a script module, so there is no need for an IIFE to prevent pollution of the global scope.
66
7- /**
8- * Emoji Settings as exported in PHP via _print_emoji_detection_script().
9- * @typedef WPEmojiSettings
10- * @type {Object }
11- * @property {?object } source The source object containing emoji-related URLs.
12- * @property {?string } source.concatemoji The URL for the concatenated emoji script.
13- * @property {?string } source.twemoji The URL for the Twemoji script.
14- * @property {?string } source.wpemoji The URL for the WP Emoji script.
15- */
7+ // Note: The WPEmojiSettings and EmojiSupports types are declared in typings/wp-emoji, since wp-emoji.js reads them back.
168
179const selector = 'script#wp-emoji-settings' ;
1810const script = document . querySelector ( selector ) ;
1911if ( ! ( script instanceof HTMLScriptElement ) ) {
20- throw new Error ( `Element missing: ${ selector } ` ) ;
12+ throw new Error ( `Element missing: ${ selector } ` ) ;
2113}
2214const settings = /** @type {WPEmojiSettings } */ ( JSON . parse ( script . text ) ) ;
2315
2416// For compatibility with other scripts that read from this global, in particular wp-includes/js/wp-emoji.js (source file: js/_enqueues/wp/emoji.js).
2517window . _wpemojiSettings = settings ;
2618
2719/**
28- * Support tests.
20+ * Results of the emoji support tests.
21+ *
2922 * @typedef SupportTests
3023 * @type {Object }
31- * @property {? boolean } flag Whether the browser supports flag emojis .
32- * @property {? boolean } emoji Whether the browser supports general emojis .
24+ * @property {boolean } flag Whether the browser renders flag emoji .
25+ * @property {boolean } emoji Whether the browser renders emoji .
3326 */
3427
3528const sessionStorageKey = 'wpEmojiSettingsSupports' ;
29+
30+ /** @type {Array<keyof SupportTests> } */
3631const tests = [ 'flag' , 'emoji' ] ;
3732
3833/**
@@ -49,16 +44,18 @@ function supportsWorkerOffloading() {
4944 typeof Worker !== 'undefined' &&
5045 typeof OffscreenCanvas !== 'undefined' &&
5146 typeof URL !== 'undefined' &&
52- URL . createObjectURL &&
47+ typeof URL . createObjectURL === 'function' &&
5348 typeof Blob !== 'undefined'
5449 ) ;
5550}
5651
5752/**
53+ * Support tests as they are stored in session storage.
54+ *
5855 * @typedef SessionSupportTests
5956 * @type {Object }
60- * @property {number } timestamp The timestamp when the support tests were last updated .
61- * @property {SupportTests } supportTests The support tests for the browser .
57+ * @property {number } timestamp When the tests were run, in milliseconds since the epoch .
58+ * @property {SupportTests } supportTests What the tests found .
6259 */
6360
6461/**
@@ -72,10 +69,13 @@ function supportsWorkerOffloading() {
7269 */
7370function getSessionSupportTests ( ) {
7471 try {
72+ const itemJson = sessionStorage . getItem ( sessionStorageKey ) ;
73+ if ( null === itemJson ) {
74+ return null ;
75+ }
76+
7577 /** @type {SessionSupportTests } */
76- const item = JSON . parse (
77- sessionStorage . getItem ( sessionStorageKey )
78- ) ;
78+ const item = JSON . parse ( itemJson ) ;
7979 if (
8080 typeof item === 'object' &&
8181 typeof item . timestamp === 'number' &&
@@ -95,7 +95,7 @@ function getSessionSupportTests() {
9595 *
9696 * @private
9797 *
98- * @param {SupportTests } supportTests Support tests.
98+ * @param {SupportTests } supportTests What the tests found .
9999 */
100100function setSessionSupportTests ( supportTests ) {
101101 try {
@@ -112,6 +112,51 @@ function setSessionSupportTests( supportTests ) {
112112 } catch ( e ) { }
113113}
114114
115+ /**
116+ * A 2D context for the support tests.
117+ *
118+ * Which of the two it is depends on the kind of canvas it came from, and the tests use only what
119+ * both provide.
120+ *
121+ * @typedef {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D } EmojiTestContext
122+ */
123+
124+ /**
125+ * Checks if two sets of Emoji characters render the same visually.
126+ *
127+ * @callback EmojiSetsRenderIdentically
128+ *
129+ * @param {EmojiTestContext } context 2D Context.
130+ * @param {string } set1 Set of Emoji to test.
131+ * @param {string } set2 Set of Emoji to test.
132+ *
133+ * @return {boolean } True if the two sets render the same.
134+ */
135+
136+ /**
137+ * Checks if the center point of a single emoji is empty.
138+ *
139+ * @callback EmojiRendersEmptyCenterPoint
140+ *
141+ * @param {EmojiTestContext } context 2D Context.
142+ * @param {string } emoji Emoji to test.
143+ *
144+ * @return {boolean } True if the center point is empty.
145+ */
146+
147+ /**
148+ * Determines if the browser properly renders Emoji that Twemoji can supplement.
149+ *
150+ * @callback BrowserSupportsEmoji
151+ *
152+ * @param {EmojiTestContext } context 2D Context.
153+ * @param {keyof SupportTests } type Which support test to run.
154+ * @param {EmojiSetsRenderIdentically } emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
155+ * @param {EmojiRendersEmptyCenterPoint } emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification.
156+ *
157+ * @return {boolean } True if the browser can render emoji, false if it cannot.
158+ */
159+
115160/**
116161 * Checks if two sets of Emoji characters render the same visually.
117162 *
@@ -127,9 +172,9 @@ function setSessionSupportTests( supportTests ) {
127172 *
128173 * @private
129174 *
130- * @param {CanvasRenderingContext2D } context 2D Context.
131- * @param {string } set1 Set of Emoji to test.
132- * @param {string } set2 Set of Emoji to test.
175+ * @param {EmojiTestContext } context 2D Context.
176+ * @param {string } set1 Set of Emoji to test.
177+ * @param {string } set2 Set of Emoji to test.
133178 *
134179 * @return {boolean } True if the two sets render the same.
135180 */
@@ -177,8 +222,8 @@ function emojiSetsRenderIdentically( context, set1, set2 ) {
177222 *
178223 * @private
179224 *
180- * @param {CanvasRenderingContext2D } context 2D Context.
181- * @param {string } emoji Emoji to test.
225+ * @param {EmojiTestContext } context 2D Context.
226+ * @param {string } emoji Emoji to test.
182227 *
183228 * @return {boolean } True if the center point is empty.
184229 */
@@ -188,7 +233,7 @@ function emojiRendersEmptyCenterPoint( context, emoji ) {
188233 context . fillText ( emoji , 0 , 0 ) ;
189234
190235 // Test if the center point (16, 16) is empty (0,0,0,0).
191- const centerPoint = context . getImageData ( 16 , 16 , 1 , 1 ) ;
236+ const centerPoint = context . getImageData ( 16 , 16 , 1 , 1 ) ;
192237 for ( let i = 0 ; i < centerPoint . data . length ; i ++ ) {
193238 if ( centerPoint . data [ i ] !== 0 ) {
194239 // Stop checking the moment it's known not to be empty.
@@ -209,14 +254,15 @@ function emojiRendersEmptyCenterPoint( context, emoji ) {
209254 *
210255 * @private
211256 *
212- * @param {CanvasRenderingContext2D } context 2D Context.
213- * @param {string } type Whether to test for support of "flag" or "emoji" .
214- * @param {Function } emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
215- * @param {Function } emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification.
257+ * @param {EmojiTestContext } context 2D Context.
258+ * @param {keyof SupportTests } type Which support test to run .
259+ * @param {EmojiSetsRenderIdentically } emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
260+ * @param {EmojiRendersEmptyCenterPoint } emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification.
216261 *
217262 * @return {boolean } True if the browser can render emoji, false if it cannot.
218263 */
219264function browserSupportsEmoji ( context , type , emojiSetsRenderIdentically , emojiRendersEmptyCenterPoint ) {
265+ /** @type {boolean } */
220266 let isIdentical ;
221267
222268 switch ( type ) {
@@ -256,7 +302,7 @@ function browserSupportsEmoji( context, type, emojiSetsRenderIdentically, emojiR
256302
257303 /*
258304 * Test for English flag compatibility. England is a country in the United Kingdom, it
259- * does not have a two letter locale code but rather a five letter sub-division code.
305+ * does not have a two letter locale code but rather a five letter subdivision code.
260306 *
261307 * To test for support, we try to render it, and compare the rendering to how it would look if
262308 * the browser doesn't render it correctly (black flag emoji + [G] + [B] + [E] + [N] + [G]).
@@ -288,8 +334,6 @@ function browserSupportsEmoji( context, type, emojiSetsRenderIdentically, emojiR
288334 const notSupported = emojiRendersEmptyCenterPoint ( context , '\uD83E\uDEC8' ) ;
289335 return ! notSupported ;
290336 }
291-
292- return false ;
293337}
294338
295339/**
@@ -302,25 +346,30 @@ function browserSupportsEmoji( context, type, emojiSetsRenderIdentically, emojiR
302346 *
303347 * @private
304348 *
305- * @param {string[] } tests Tests .
306- * @param {Function } browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification.
307- * @param {Function } emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
308- * @param {Function } emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification.
349+ * @param {Array<keyof SupportTests> } tests Which support tests to run .
350+ * @param {BrowserSupportsEmoji } browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification.
351+ * @param {EmojiSetsRenderIdentically } emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
352+ * @param {EmojiRendersEmptyCenterPoint } emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification.
309353 *
310354 * @return {SupportTests } Support tests.
311355 */
312356function testEmojiSupports ( tests , browserSupportsEmoji , emojiSetsRenderIdentically , emojiRendersEmptyCenterPoint ) {
313- let canvas ;
357+ /** @type {?EmojiTestContext } */
358+ let context ;
359+
314360 if (
315361 typeof WorkerGlobalScope !== 'undefined' &&
316362 self instanceof WorkerGlobalScope
317363 ) {
318- canvas = new OffscreenCanvas ( 300 , 150 ) ; // Dimensions are default for HTMLCanvasElement.
364+ // Dimensions are default for HTMLCanvasElement.
365+ context = new OffscreenCanvas ( 300 , 150 ) . getContext ( '2d' , { willReadFrequently : true } ) ;
319366 } else {
320- canvas = document . createElement ( 'canvas' ) ;
367+ context = document . createElement ( 'canvas' ) . getContext ( '2d' , { willReadFrequently : true } ) ;
321368 }
322369
323- const context = canvas . getContext ( '2d' , { willReadFrequently : true } ) ;
370+ if ( ! context ) {
371+ throw new Error ( 'Unable to obtain a 2D context for the emoji support tests.' ) ;
372+ }
324373
325374 /*
326375 * Chrome on OS X added native emoji rendering in M41. Unfortunately,
@@ -330,7 +379,7 @@ function testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentica
330379 context . textBaseline = 'top' ;
331380 context . font = '600 32px Arial' ;
332381
333- const supports = { } ;
382+ const supports = /** @type { SupportTests } */ ( { } ) ;
334383 tests . forEach ( ( test ) => {
335384 supports [ test ] = browserSupportsEmoji ( context , test , emojiSetsRenderIdentically , emojiRendersEmptyCenterPoint ) ;
336385 } ) ;
@@ -361,10 +410,11 @@ settings.supports = {
361410} ;
362411
363412// Obtain the emoji support from the browser, asynchronously when possible.
364- new Promise ( ( resolve ) => {
365- let supportTests = getSessionSupportTests ( ) ;
366- if ( supportTests ) {
367- resolve ( supportTests ) ;
413+ /** @type {Promise<SupportTests> } */
414+ const supportTestsPromise = new Promise ( ( resolve ) => {
415+ const sessionSupportTests = getSessionSupportTests ( ) ;
416+ if ( sessionSupportTests ) {
417+ resolve ( sessionSupportTests ) ;
368418 return ;
369419 }
370420
@@ -386,36 +436,39 @@ new Promise( ( resolve ) => {
386436 type : 'text/javascript'
387437 } ) ;
388438 const worker = new Worker ( URL . createObjectURL ( blob ) , { name : 'wpTestEmojiSupports' } ) ;
389- worker . onmessage = ( event ) => {
390- supportTests = event . data ;
391- setSessionSupportTests ( supportTests ) ;
439+ worker . onmessage = ( /** @type {MessageEvent<SupportTests> } */ event ) => {
440+ setSessionSupportTests ( event . data ) ;
392441 worker . terminate ( ) ;
393- resolve ( supportTests ) ;
442+ resolve ( event . data ) ;
394443 } ;
395444 return ;
396445 } catch ( e ) { }
397446 }
398447
399- supportTests = testEmojiSupports ( tests , browserSupportsEmoji , emojiSetsRenderIdentically , emojiRendersEmptyCenterPoint ) ;
400- setSessionSupportTests ( supportTests ) ;
401- resolve ( supportTests ) ;
402- } )
448+ const testedSupportTests = testEmojiSupports ( tests , browserSupportsEmoji , emojiSetsRenderIdentically , emojiRendersEmptyCenterPoint ) ;
449+ setSessionSupportTests ( testedSupportTests ) ;
450+ resolve ( testedSupportTests ) ;
451+ } ) ;
452+
453+ supportTestsPromise
403454 // Once the browser emoji support has been obtained from the session, finalize the settings.
404455 . then ( ( supportTests ) => {
405456 /*
406457 * Tests the browser support for flag emojis and other emojis, and adjusts the
407458 * support settings accordingly.
408459 */
409460 for ( const test in supportTests ) {
410- settings . supports [ test ] = supportTests [ test ] ;
461+ const key = /** @type {keyof SupportTests } */ ( test ) ;
462+ const supported = supportTests [ key ] ;
463+
464+ settings . supports [ key ] = supported ;
411465
412466 settings . supports . everything =
413- settings . supports . everything && settings . supports [ test ] ;
467+ settings . supports . everything && supported ;
414468
415- if ( 'flag' !== test ) {
469+ if ( 'flag' !== key ) {
416470 settings . supports . everythingExceptFlag =
417- settings . supports . everythingExceptFlag &&
418- settings . supports [ test ] ;
471+ settings . supports . everythingExceptFlag && supported ;
419472 }
420473 }
421474
0 commit comments