Index: src/wp-includes/formatting.php
===================================================================
--- src/wp-includes/formatting.php	(revision 45484)
+++ src/wp-includes/formatting.php	(working copy)
@@ -2465,12 +2465,19 @@
 	// WP bug fix for LOVE <3 (and other situations with '<' before a number)
 	$text = preg_replace( '#<([0-9]{1})#', '&lt;$1', $text );
 
-	while ( preg_match( '/<(\/?[\w:]*)\s*([^>]*)>/', $text, $regex ) ) {
+	while ( preg_match( '/<(\/?[a-z0-9]+-[a-z0-9-]+|\/?[\w:]*)(\s*)([^>]*)>/', $text, $regex ) ) {
 		$newtext .= $tagqueue;
 
 		$i = strpos( $text, $regex[0] );
 		$l = strlen( $regex[0] );
 
+		// Do not modify since tags shouldn't end in a dash.
+		if ( empty( $regex[2] ) && isset( $regex[3][0] ) && '-' === $regex[3][0] ) {
+			$newtext .= substr( $text, 0, $i + $l );
+			$text     = substr( $text, $i + $l );
+			continue;
+		}
+
 		// clear the shifter
 		$tagqueue = '';
 		// Pop or Push
@@ -2508,14 +2515,14 @@
 			// If it's an empty tag "< >", do nothing
 			if ( '' == $tag ) {
 				// do nothing
-			} elseif ( substr( $regex[2], -1 ) == '/' ) { // ElseIf it presents itself as a self-closing tag...
+			} elseif ( substr( $regex[3], -1 ) == '/' ) { // ElseIf it presents itself as a self-closing tag...
 				// ...but it isn't a known single-entity self-closing tag, then don't let it be treated as such and
 				// immediately close it with a closing tag (the tag will encapsulate no text as a result)
 				if ( ! in_array( $tag, $single_tags ) ) {
-					$regex[2] = trim( substr( $regex[2], 0, -1 ) ) . "></$tag";
+					$regex[3] = trim( substr( $regex[3], 0, -1 ) ) . "></$tag";
 				}
 			} elseif ( in_array( $tag, $single_tags ) ) { // ElseIf it's a known single-entity tag but it doesn't close itself, do so
-				$regex[2] .= '/';
+				$regex[3] .= '/';
 			} else { // Else it's not a single-entity tag
 				// If the top of the stack is the same as the tag we want to push, close previous tag
 				if ( $stacksize > 0 && ! in_array( $tag, $nestable_tags ) && $tagstack[ $stacksize - 1 ] == $tag ) {
@@ -2526,7 +2533,7 @@
 			}
 
 			// Attributes
-			$attributes = $regex[2];
+			$attributes = $regex[3];
 			if ( ! empty( $attributes ) && $attributes[0] != '>' ) {
 				$attributes = ' ' . $attributes;
 			}
@@ -3040,9 +3047,7 @@
 function wp_targeted_link_rel( $text ) {
 	// Don't run (more expensive) regex if no links with targets.
 	if ( stripos( $text, 'target' ) !== false && stripos( $text, '<a ' ) !== false ) {
-		if ( ! is_serialized( $text ) ) {
-			$text = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text );
-		}
+		$text = preg_replace_callback( '|<a\s([^>]*target\s*=[^>]*)>|i', 'wp_targeted_link_rel_callback', $text );
 	}
 
 	return $text;
@@ -3096,10 +3101,6 @@
 		$delimiter = trim( $rel_match[1] ) ? $rel_match[1] : '"';
 		$rel       = 'rel=' . $delimiter . trim( implode( ' ', $parts ) ) . $delimiter;
 		$link_html = str_replace( $rel_match[0], $rel, $link_html );
-	} elseif ( preg_match( '|target\s*=\s*?\\\\"|', $link_html ) ) {
-		$link_html .= " rel=\\\"$rel\\\"";
-	} elseif ( preg_match( '#(target|href)\s*=\s*?\'#', $link_html ) ) {
-		$link_html .= " rel='$rel'";
 	} else {
 		$link_html .= " rel=\"$rel\"";
 	}
@@ -3432,13 +3433,13 @@
 	if ( $tz ) {
 		$datetime = date_create( $string, new DateTimeZone( 'UTC' ) );
 		if ( ! $datetime ) {
-			return gmdate( $format, 0 );
+			return date( $format, 0 );
 		}
 		$datetime->setTimezone( new DateTimeZone( $tz ) );
 		$string_localtime = $datetime->format( $format );
 	} else {
 		if ( ! preg_match( '#([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})#', $string, $matches ) ) {
-			return gmdate( $format, 0 );
+			return date( $format, 0 );
 		}
 		$string_time      = gmmktime( $matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1] );
 		$string_localtime = gmdate( $format, $string_time + get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
@@ -4811,8 +4812,8 @@
  * @since 2.5.0
  * @link https://secure.php.net/sprintf
  *
- * @param string $pattern The string which formatted args are inserted.
- * @param mixed  ...$args Arguments to be formatted into the $pattern string.
+ * @param string $pattern   The string which formatted args are inserted.
+ * @param mixed  $args ,... Arguments to be formatted into the $pattern string.
  * @return string The formatted string.
  */
 function wp_sprintf( $pattern ) {
@@ -5520,7 +5521,7 @@
 
 		/*
 		 * If you're looking at a src version of this file, you'll see an "include"
-		 * statement below. This is used by the `npm run build` process to directly
+		 * statement below. This is used by the `grunt build` process to directly
 		 * include a minified version of wp-emoji-loader.js, instead of using the
 		 * readfile() method from above.
 		 *
@@ -5531,7 +5532,7 @@
 		?>
 		<script type="text/javascript">
 			window._wpemojiSettings = <?php echo wp_json_encode( $settings ); ?>;
-			include "js/wp-emoji-loader.min.js"
+			!function(a,b,c){function d(a,b){var c=String.fromCharCode;l.clearRect(0,0,k.width,k.height),l.fillText(c.apply(this,a),0,0);var d=k.toDataURL();l.clearRect(0,0,k.width,k.height),l.fillText(c.apply(this,b),0,0);var e=k.toDataURL();return d===e}function e(a){var b;if(!l||!l.fillText)return!1;switch(l.textBaseline="top",l.font="600 32px Arial",a){case"flag":return!(b=d([55356,56826,55356,56819],[55356,56826,8203,55356,56819]))&&(b=d([55356,57332,56128,56423,56128,56418,56128,56421,56128,56430,56128,56423,56128,56447],[55356,57332,8203,56128,56423,8203,56128,56418,8203,56128,56421,8203,56128,56430,8203,56128,56423,8203,56128,56447]),!b);case"emoji":return b=d([55357,56424,55356,57342,8205,55358,56605,8205,55357,56424,55356,57340],[55357,56424,55356,57342,8203,55358,56605,8203,55357,56424,55356,57340]),!b}return!1}function f(a){var c=b.createElement("script");c.src=a,c.defer=c.type="text/javascript",b.getElementsByTagName("head")[0].appendChild(c)}var g,h,i,j,k=b.createElement("canvas"),l=k.getContext&&k.getContext("2d");for(j=Array("flag","emoji"),c.supports={everything:!0,everythingExceptFlag:!0},i=0;i<j.length;i++)c.supports[j[i]]=e(j[i]),c.supports.everything=c.supports.everything&&c.supports[j[i]],"flag"!==j[i]&&(c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&c.supports[j[i]]);c.supports.everythingExceptFlag=c.supports.everythingExceptFlag&&!c.supports.flag,c.DOMReady=!1,c.readyCallback=function(){c.DOMReady=!0},c.supports.everything||(h=function(){c.readyCallback()},b.addEventListener?(b.addEventListener("DOMContentLoaded",h,!1),a.addEventListener("load",h,!1)):(a.attachEvent("onload",h),b.attachEvent("onreadystatechange",function(){"complete"===b.readyState&&c.readyCallback()})),g=c.source||{},g.concatemoji?f(g.concatemoji):g.wpemoji&&g.twemoji&&(f(g.twemoji),f(g.wpemoji)))}(window,document,window._wpemojiSettings);
 		</script>
 		<?php
 	}
@@ -5736,7 +5737,7 @@
  * Returns arrays of emoji data.
  *
  * These arrays are automatically built from the regex in twemoji.js - if they need to be updated,
- * you should update the regex there, then run the `npm run grunt precommit:emoji` job.
+ * you should update the regex there, then run the `grunt precommit:emoji` job.
  *
  * @since 4.9.0
  * @access private
Index: tests/phpunit/tests/formatting/balanceTags.php
===================================================================
--- tests/phpunit/tests/formatting/balanceTags.php	(revision 45484)
+++ tests/phpunit/tests/formatting/balanceTags.php	(working copy)
@@ -68,6 +68,7 @@
 			'<em />',
 			'<p class="main1"/>',
 			'<p class="main2" />',
+			'<STRONG/>',
 		);
 		$expected = array(
 			'<strong></strong>',
@@ -74,6 +75,8 @@
 			'<em></em>',
 			'<p class="main1"></p>',
 			'<p class="main2"></p>',
+			// Valid tags are transformed to lowercase.
+			'<strong></strong>',
 		);
 
 		foreach ( $inputs as $key => $input ) {
@@ -221,4 +224,56 @@
 		}
 	}
 
+	/**
+	 * Get custom element data.
+	 *
+	 * @return array Data.
+	 */
+	public function data_custom_elements() {
+		return array(
+			// Valid custom element tags.
+			array(
+				'<my-custom-element>Test</my-custom-element>',
+				'<my-custom-element>Test</my-custom-element>',
+			),
+			array(
+				'<my-custom-element>Test',
+				'<my-custom-element>Test</my-custom-element>',
+			),
+			array(
+				'Test</my-custom-element>',
+				'Test',
+			),
+			array(
+				'</my-custom-element>Test',
+				'Test',
+			),
+			// Invalid (or at least temporarily unsupported) custom element tags.
+			array(
+				'<MY-CUSTOM-ELEMENT>Test',
+				'<MY-CUSTOM-ELEMENT>Test',
+			),
+			array(
+				'<my->Test',
+				'<my->Test',
+			),
+			array(
+				'<--->Test',
+				'<--->Test',
+			),
+		);
+	}
+
+	/**
+	 * Test custom elements.
+	 *
+	 * @ticket 47014
+	 * @dataProvider data_custom_elements
+	 *
+	 * @param string $source   Source.
+	 * @param string $expected Expected.
+	 */
+	public function test_custom_elements( $source, $expected ) {
+		$this->assertEquals( $expected, balanceTags( $source, true ) );
+	}
 }
