wp/wp-includes/js/wp-emoji-loader.js
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
   115 	}
   115 	}
   116 
   116 
   117 	/**
   117 	/**
   118 	 * Checks if two sets of Emoji characters render the same visually.
   118 	 * Checks if two sets of Emoji characters render the same visually.
   119 	 *
   119 	 *
       
   120 	 * This is used to determine if the browser is rendering an emoji with multiple data points
       
   121 	 * correctly. set1 is the emoji in the correct form, using a zero-width joiner. set2 is the emoji
       
   122 	 * in the incorrect form, using a zero-width space. If the two sets render the same, then the browser
       
   123 	 * does not support the emoji correctly.
       
   124 	 *
   120 	 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
   125 	 * 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.
   126 	 * scope. Everything must be passed by parameters.
   122 	 *
   127 	 *
   123 	 * @since 4.9.0
   128 	 * @since 4.9.0
   124 	 *
   129 	 *
   159 			return rendered2Data === rendered2[ index ];
   164 			return rendered2Data === rendered2[ index ];
   160 		} );
   165 		} );
   161 	}
   166 	}
   162 
   167 
   163 	/**
   168 	/**
       
   169 	 * Checks if the center point of a single emoji is empty.
       
   170 	 *
       
   171 	 * This is used to determine if the browser is rendering an emoji with a single data point
       
   172 	 * correctly. The center point of an incorrectly rendered emoji will be empty. A correctly
       
   173 	 * rendered emoji will have a non-zero value at the center point.
       
   174 	 *
       
   175 	 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
       
   176 	 * scope. Everything must be passed by parameters.
       
   177 	 *
       
   178 	 * @since 6.8.2
       
   179 	 *
       
   180 	 * @private
       
   181 	 *
       
   182 	 * @param {CanvasRenderingContext2D} context 2D Context.
       
   183 	 * @param {string} emoji Emoji to test.
       
   184 	 *
       
   185 	 * @return {boolean} True if the center point is empty.
       
   186 	 */
       
   187 	function emojiRendersEmptyCenterPoint( context, emoji ) {
       
   188 		// Cleanup from previous test.
       
   189 		context.clearRect( 0, 0, context.canvas.width, context.canvas.height );
       
   190 		context.fillText( emoji, 0, 0 );
       
   191 
       
   192 		// Test if the center point (16, 16) is empty (0,0,0,0).
       
   193 		var centerPoint = context.getImageData(16, 16, 1, 1);
       
   194 		for ( var i = 0; i < centerPoint.data.length; i++ ) {
       
   195 			if ( centerPoint.data[ i ] !== 0 ) {
       
   196 				// Stop checking the moment it's known not to be empty.
       
   197 				return false;
       
   198 			}
       
   199 		}
       
   200 
       
   201 		return true;
       
   202 	}
       
   203 
       
   204 	/**
   164 	 * Determines if the browser properly renders Emoji that Twemoji can supplement.
   205 	 * Determines if the browser properly renders Emoji that Twemoji can supplement.
   165 	 *
   206 	 *
   166 	 * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing
   207 	 * 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.
   208 	 * scope. Everything must be passed by parameters.
   168 	 *
   209 	 *
   171 	 * @private
   212 	 * @private
   172 	 *
   213 	 *
   173 	 * @param {CanvasRenderingContext2D} context 2D Context.
   214 	 * @param {CanvasRenderingContext2D} context 2D Context.
   174 	 * @param {string} type Whether to test for support of "flag" or "emoji".
   215 	 * @param {string} type Whether to test for support of "flag" or "emoji".
   175 	 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
   216 	 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
       
   217 	 * @param {Function} emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification.
   176 	 *
   218 	 *
   177 	 * @return {boolean} True if the browser can render emoji, false if it cannot.
   219 	 * @return {boolean} True if the browser can render emoji, false if it cannot.
   178 	 */
   220 	 */
   179 	function browserSupportsEmoji( context, type, emojiSetsRenderIdentically ) {
   221 	function browserSupportsEmoji( context, type, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) {
   180 		var isIdentical;
   222 		var isIdentical;
   181 
   223 
   182 		switch ( type ) {
   224 		switch ( type ) {
   183 			case 'flag':
   225 			case 'flag':
   184 				/*
   226 				/*
   196 				if ( isIdentical ) {
   238 				if ( isIdentical ) {
   197 					return false;
   239 					return false;
   198 				}
   240 				}
   199 
   241 
   200 				/*
   242 				/*
   201 				 * Test for UN flag compatibility. This is the least supported of the letter locale flags,
   243 				 * Test for Sark flag compatibility. This is the least supported of the letter locale flags,
   202 				 * so gives us an easy test for full support.
   244 				 * so gives us an easy test for full support.
   203 				 *
   245 				 *
   204 				 * To test for support, we try to render it, and compare the rendering to how it would look if
   246 				 * 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]).
   247 				 * the browser doesn't render it correctly ([C] + [Q]).
   206 				 */
   248 				 */
   207 				isIdentical = emojiSetsRenderIdentically(
   249 				isIdentical = emojiSetsRenderIdentically(
   208 					context,
   250 					context,
   209 					'\uD83C\uDDFA\uD83C\uDDF3', // as the sequence of two code points
   251 					'\uD83C\uDDE8\uD83C\uDDF6', // as the sequence of two code points
   210 					'\uD83C\uDDFA\u200B\uD83C\uDDF3' // as the two code points separated by a zero-width space
   252 					'\uD83C\uDDE8\u200B\uD83C\uDDF6' // as the two code points separated by a zero-width space
   211 				);
   253 				);
   212 
   254 
   213 				if ( isIdentical ) {
   255 				if ( isIdentical ) {
   214 					return false;
   256 					return false;
   215 				}
   257 				}
   230 				);
   272 				);
   231 
   273 
   232 				return ! isIdentical;
   274 				return ! isIdentical;
   233 			case 'emoji':
   275 			case 'emoji':
   234 				/*
   276 				/*
   235 				 * Four and twenty blackbirds baked in a pie.
   277 				 * Does Emoji 16.0 cause the browser to go splat?
   236 				 *
   278 				 *
   237 				 * To test for Emoji 15.0 support, try to render a new emoji: Blackbird.
   279 				 * To test for Emoji 16.0 support, try to render a new emoji: Splatter.
   238 				 *
   280 				 *
   239 				 * The Blackbird is a ZWJ sequence combining 🐦 Bird and ⬛ large black square.,
   281 				 * The splatter emoji is a single code point emoji. Testing for browser support
   240 				 *
   282 				 * required testing the center point of the emoji to see if it is empty.
   241 				 * 0x1F426 (\uD83D\uDC26) == Bird
   283 				 *
   242 				 * 0x200D == Zero-Width Joiner (ZWJ) that links the code points for the new emoji or
   284 				 * 0xD83E 0xDEDF (\uD83E\uDEDF) == 🫟 Splatter.
   243 				 * 0x200B == Zero-Width Space (ZWS) that is rendered for clients not supporting the new emoji.
   285 				 *
   244 				 * 0x2B1B == Large Black Square
   286 				 * When updating this test, please ensure that the emoji is either a single code point
   245 				 *
   287 				 * or switch to using the emojiSetsRenderIdentically function and testing with a zero-width
   246 				 * When updating this test for future Emoji releases, ensure that individual emoji that make up the
   288 				 * joiner vs a zero-width space.
   247 				 * sequence come from older emoji standards.
       
   248 				 */
   289 				 */
   249 				isIdentical = emojiSetsRenderIdentically(
   290 				var notSupported = emojiRendersEmptyCenterPoint( context, '\uD83E\uDEDF' );
   250 					context,
   291 				return ! notSupported;
   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 		}
   292 		}
   257 
   293 
   258 		return false;
   294 		return false;
   259 	}
   295 	}
   260 
   296 
   269 	 * @private
   305 	 * @private
   270 	 *
   306 	 *
   271 	 * @param {string[]} tests Tests.
   307 	 * @param {string[]} tests Tests.
   272 	 * @param {Function} browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification.
   308 	 * @param {Function} browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification.
   273 	 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
   309 	 * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification.
       
   310 	 * @param {Function} emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification.
   274 	 *
   311 	 *
   275 	 * @return {SupportTests} Support tests.
   312 	 * @return {SupportTests} Support tests.
   276 	 */
   313 	 */
   277 	function testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically ) {
   314 	function testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) {
   278 		var canvas;
   315 		var canvas;
   279 		if (
   316 		if (
   280 			typeof WorkerGlobalScope !== 'undefined' &&
   317 			typeof WorkerGlobalScope !== 'undefined' &&
   281 			self instanceof WorkerGlobalScope
   318 			self instanceof WorkerGlobalScope
   282 		) {
   319 		) {
   295 		context.textBaseline = 'top';
   332 		context.textBaseline = 'top';
   296 		context.font = '600 32px Arial';
   333 		context.font = '600 32px Arial';
   297 
   334 
   298 		var supports = {};
   335 		var supports = {};
   299 		tests.forEach( function ( test ) {
   336 		tests.forEach( function ( test ) {
   300 			supports[ test ] = browserSupportsEmoji( context, test, emojiSetsRenderIdentically );
   337 			supports[ test ] = browserSupportsEmoji( context, test, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint );
   301 		} );
   338 		} );
   302 		return supports;
   339 		return supports;
   303 	}
   340 	}
   304 
   341 
   305 	/**
   342 	/**
   348 					testEmojiSupports.toString() +
   385 					testEmojiSupports.toString() +
   349 					'(' +
   386 					'(' +
   350 					[
   387 					[
   351 						JSON.stringify( tests ),
   388 						JSON.stringify( tests ),
   352 						browserSupportsEmoji.toString(),
   389 						browserSupportsEmoji.toString(),
   353 						emojiSetsRenderIdentically.toString()
   390 						emojiSetsRenderIdentically.toString(),
       
   391 						emojiRendersEmptyCenterPoint.toString()
   354 					].join( ',' ) +
   392 					].join( ',' ) +
   355 					'));';
   393 					'));';
   356 				var blob = new Blob( [ workerScript ], {
   394 				var blob = new Blob( [ workerScript ], {
   357 					type: 'text/javascript'
   395 					type: 'text/javascript'
   358 				} );
   396 				} );
   365 				};
   403 				};
   366 				return;
   404 				return;
   367 			} catch ( e ) {}
   405 			} catch ( e ) {}
   368 		}
   406 		}
   369 
   407 
   370 		supportTests = testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically );
   408 		supportTests = testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint );
   371 		setSessionSupportTests( supportTests );
   409 		setSessionSupportTests( supportTests );
   372 		resolve( supportTests );
   410 		resolve( supportTests );
   373 	} )
   411 	} )
   374 		// Once the browser emoji support has been obtained from the session, finalize the settings.
   412 		// Once the browser emoji support has been obtained from the session, finalize the settings.
   375 		.then( function ( supportTests ) {
   413 		.then( function ( supportTests ) {