Index: src/wp-includes/formatting.php
===================================================================
--- src/wp-includes/formatting.php	(revision 39198)
+++ src/wp-includes/formatting.php	(working copy)
@@ -25,7 +25,6 @@
  * @since 0.71
  *
  * @global array $wp_cockneyreplace Array of formatted entities for certain common phrases
- * @global array $shortcode_tags
  * @staticvar array $static_characters
  * @staticvar array $static_replacements
  * @staticvar array $dynamic_characters
@@ -39,7 +38,7 @@
  * @return string The string replaced with html entities
  */
 function wptexturize( $text, $reset = false ) {
-	global $wp_cockneyreplace, $shortcode_tags;
+	global $wp_cockneyreplace;
 	static $static_characters = null,
 		$static_replacements = null,
 		$dynamic_characters = null,
@@ -216,8 +215,7 @@
 
 	// Look for shortcodes and HTML elements.
 
-	preg_match_all( '@\[/?([^<>&/\[\]\x00-\x20=]++)@', $text, $matches );
-	$tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
+	$tagnames = get_shortcode_tagnames( $text );
 	$found_shortcodes = ! empty( $tagnames );
 	$shortcode_regex = $found_shortcodes ? _get_wptexturize_shortcode_regex( $tagnames ) : '';
 	$regex = _get_wptexturize_split_regex( $shortcode_regex );
@@ -696,7 +694,7 @@
  */
 function _get_wptexturize_shortcode_regex( $tagnames ) {
 	$tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
-	$tagregexp = "(?:$tagregexp)(?=[\\s\\]\\/])"; // Excerpt of get_shortcode_regex().
+	$tagregexp = "(?:$tagregexp)(?=" . shortcode_name_terminators() . ")"; // Excerpt of get_shortcode_regex().
 	$regex =
 		  '\['              // Find start of shortcode.
 		. '[\/\[]?'         // Shortcodes may begin with [/ or [[
@@ -782,19 +780,17 @@
  *
  * @since 2.9.0
  *
- * @global array $shortcode_tags
- *
  * @param string $pee The content.
  * @return string The filtered content.
  */
 function shortcode_unautop( $pee ) {
-	global $shortcode_tags;
 
-	if ( empty( $shortcode_tags ) || !is_array( $shortcode_tags ) ) {
+	$tagnames = get_shortcode_tagnames( $pee );
+	if ( empty( $tagnames ) ) {
 		return $pee;
 	}
 
-	$tagregexp = join( '|', array_map( 'preg_quote', array_keys( $shortcode_tags ) ) );
+	$tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) );
 	$spaces = wp_spaces_regexp();
 
 	$pattern =
@@ -804,7 +800,7 @@
 		. '('                                // 1: The shortcode
 		.     '\\['                          // Opening bracket
 		.     "($tagregexp)"                 // 2: Shortcode name
-		.     '(?![\\w-])'                   // Not followed by word character or hyphen
+		.     '(?=' . shortcode_name_terminators() . ')' // Followed by a shortcode name terminator.
 		                                     // Unroll the loop: Inside the opening shortcode tag
 		.     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
 		.     '(?:'
Index: src/wp-includes/js/shortcode.js
===================================================================
--- src/wp-includes/js/shortcode.js	(revision 39198)
+++ src/wp-includes/js/shortcode.js	(working copy)
@@ -102,7 +102,8 @@
 		// 6. The closing tag.
 		// 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
 		regexp: _.memoize( function( tag ) {
-			return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
+			var shortcode_name_terminators = '[<>&\\/\\[\\]\x00-\x20=\u00a0]';
+			return new RegExp( '\\[(\\[?)(' + tag + ')(?=' + shortcode_name_terminators + ')([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
 		}),
 
 
Index: src/wp-includes/shortcodes.php
===================================================================
--- src/wp-includes/shortcodes.php	(revision 39198)
+++ src/wp-includes/shortcodes.php	(working copy)
@@ -95,7 +95,7 @@
 		return;
 	}
 
-	if ( 0 !== preg_match( '@[<>&/\[\]\x00-\x20=]@', $tag ) ) {
+	if ( 0 !== preg_match( '@' . shortcode_name_terminators() . '@', $tag ) ) {
 		/* translators: 1: shortcode name, 2: space separated list of reserved characters */
 		$message = sprintf( __( 'Invalid shortcode name: %1$s. Do not use spaces or reserved characters: %2$s' ), $tag, '& / < > [ ] =' );
 		_doing_it_wrong( __FUNCTION__, $message, '4.4.0' );
@@ -157,8 +157,6 @@
  *
  * @since 3.6.0
  *
- * @global array $shortcode_tags
- *
  * @param string $content Content to search for shortcodes.
  * @param string $tag     Shortcode tag to check.
  * @return bool Whether the passed content contains the given shortcode.
@@ -193,25 +191,14 @@
  *
  * @since 2.5.0
  *
- * @global array $shortcode_tags List of shortcode tags and their callback hooks.
- *
  * @param string $content Content to search for shortcodes.
  * @param bool $ignore_html When true, shortcodes inside HTML elements will be skipped.
  * @return string Content with shortcodes filtered out.
  */
 function do_shortcode( $content, $ignore_html = false ) {
-	global $shortcode_tags;
 
-	if ( false === strpos( $content, '[' ) ) {
-		return $content;
-	}
-
-	if (empty($shortcode_tags) || !is_array($shortcode_tags))
-		return $content;
-
 	// Find all registered tag names in $content.
-	preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
-	$tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] );
+	$tagnames = get_shortcode_tagnames( $content );
 
 	if ( empty( $tagnames ) ) {
 		return $content;
@@ -245,16 +232,13 @@
  *
  * @since 2.5.0
  *
- * @global array $shortcode_tags
- *
  * @param array $tagnames List of shortcodes to find. Optional. Defaults to all registered shortcodes.
  * @return string The shortcode search regular expression
  */
 function get_shortcode_regex( $tagnames = null ) {
-	global $shortcode_tags;
 
 	if ( empty( $tagnames ) ) {
-		$tagnames = array_keys( $shortcode_tags );
+		$tagnames = get_shortcode_tagnames();
 	}
 	$tagregexp = join( '|', array_map('preg_quote', $tagnames) );
 
@@ -264,7 +248,7 @@
 		  '\\['                              // Opening bracket
 		. '(\\[?)'                           // 1: Optional second opening bracket for escaping shortcodes: [[tag]]
 		. "($tagregexp)"                     // 2: Shortcode name
-		. '(?![\\w-])'                       // Not followed by word character or hyphen
+		. '(?=' . shortcode_name_terminators() . ')' // Followed by a shortcode name terminator.
 		. '('                                // 3: Unroll the loop: Inside the opening shortcode tag
 		.     '[^\\]\\/]*'                   // Not a closing bracket or forward slash
 		.     '(?:'
@@ -292,6 +276,72 @@
 }
 
 /**
+ * Return regular expression of characters that terminate the name of a shortcode.
+ * Dependent on charset of blog.
+ *
+ * @since 4.7.0
+ *
+ * @return string Regular expression of terminating characters.
+ */
+function shortcode_name_terminators() {
+	// Needs to be synced with get_shortcode_tagnames() expression below.
+	if ( 'UTF-8' === _canonical_charset( get_option( 'blog_charset' ) ) ) {
+		return '(?:[<>&\/\[\]\x00-\x20=]|\xc2\xa0)';
+	}
+	return '[<>&\/\[\]\x00-\x20=]';
+}
+
+/**
+ * Return registered shortcode tagnames, reduced to those in $text if given.
+ *
+ * @since 4.7.0
+ *
+ * @global array $shortcode_tags
+ *
+ * @param string $text Optional. If given, only those registered shortcodes that appear in $text are returned. 
+ * @return array Array of shortcode names.
+ */
+function get_shortcode_tagnames( $text = null ) {
+	global $shortcode_tags;
+
+	if ( ! $shortcode_tags || ! is_array( $shortcode_tags ) ) {
+		return array();
+	}
+	if ( null === $text ) {
+		return array_keys( $shortcode_tags );
+	}
+	if ( false === strpos( $text, '[' ) ) {
+		return array();
+	}
+
+	// Grouped expressions with unlimited repetition cause seg faults or match failures in PCRE <= 8.12 so use simple character class first and then post-process the matches.
+	if ( ! preg_match_all( '/\[(?!\[)\/?\K[^<>&\/\[\]\x00-\x20=]+/', $text, $matches ) ) { // Needs to be synced with shortcode_name_terminators() expression above.
+		return array();
+	}
+	if ( false !== strpos( $text, "\xc2\xa0" ) && 'UTF-8' === _canonical_charset( get_option( 'blog_charset' ) ) ) {
+		$matches[0] = array_map( '_shortcode_name_terminator_chop_cb', $matches[0] );
+	}
+
+	return array_values( array_intersect( array_keys( $shortcode_tags ), $matches[0] ) );
+}
+
+/**
+ * Callback for array_map in {@see get_shortcode_tagnames()} to chop off UTF-8 no-break space from potential shortcode name.
+ *
+ * @since 4.7.0
+ * @access private
+ *
+ * @param string $entry The potential shortcode name match.
+ * @return string The potential shortcode name chopped down if necessary.
+ */
+function _shortcode_name_terminator_chop_cb( $entry ) {
+	if ( false !== ( $pos = strpos( $entry, "\xc2\xa0" ) ) ) {
+		$entry = substr( $entry, 0, $pos );
+	}
+	return $entry;
+}
+
+/**
  * Regular Expression callable for do_shortcode() for calling shortcode hook.
  * @see get_shortcode_regex for details of the match array contents.
  *
@@ -588,38 +638,24 @@
  *
  * @since 2.5.0
  *
- * @global array $shortcode_tags
- *
  * @param string $content Content to remove shortcode tags.
  * @return string Content without shortcode tags.
  */
 function strip_shortcodes( $content ) {
-	global $shortcode_tags;
 
-	if ( false === strpos( $content, '[' ) ) {
-		return $content;
-	}
-
-	if (empty($shortcode_tags) || !is_array($shortcode_tags))
-		return $content;
-
 	// Find all registered tag names in $content.
-	preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches );
+	$tagnames = get_shortcode_tagnames( $content );
 
-	$tags_to_remove = array_keys( $shortcode_tags );
-
 	/**
 	 * Filters the list of shortcode tags to remove from the content.
 	 *
 	 * @since 4.7.0
 	 *
-	 * @param array  $tag_array Array of shortcode tags to remove.
+	 * @param array  $tagnames  Array of shortcode tags to remove.
 	 * @param string $content   Content shortcodes are being removed from.
 	 */
-	$tags_to_remove = apply_filters( 'strip_shortcodes_tagnames', $tags_to_remove, $content );
+	$tagnames = apply_filters( 'strip_shortcodes_tagnames', $tagnames, $content );
 
-	$tagnames = array_intersect( $tags_to_remove, $matches[1] );
-
 	if ( empty( $tagnames ) ) {
 		return $content;
 	}
Index: tests/phpunit/tests/shortcode.php
===================================================================
--- tests/phpunit/tests/shortcode.php	(revision 39198)
+++ tests/phpunit/tests/shortcode.php	(working copy)
@@ -332,6 +332,51 @@
 	}
 
 	/**
+	 * @ticket 35022
+	 */
+	function test_no_break_space_following_shortcode_tag() {
+		do_shortcode( "[test-shortcode-tag\xC2\xA0foo=\"bar\"]" );
+		$this->assertSame( 'test-shortcode-tag', $this->tagname );
+		$this->assertSame( array( 'foo' => 'bar' ), $this->atts );
+	}
+
+	/**
+	 * @ticket 35022
+	 */
+	function test_non_no_break_space_following_shortcode_tag() {
+		do_shortcode( "[test-shortcode-tag\xC2\xA1foo=\"bar\"]" );
+		$this->assertNull( $this->tagname );
+		$this->assertNull( $this->atts );
+	}
+
+	function _charset_iso_8859_1() {
+		return 'iso-8859-1';
+	}
+
+	/**
+	 * @ticket 35022
+	 */
+	function test_no_break_space_following_shortcode_tag_iso_8859_1() {
+		add_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) );
+
+		do_shortcode( "[test-shortcode-tag foo=\"bar\"]" );
+		$this->assertSame( 'test-shortcode-tag', $this->tagname );
+		$this->assertSame( array( 'foo' => 'bar' ), $this->atts );
+
+		$this->atts = $this->content = $this->tagname = null;
+		do_shortcode( "[test-shortcode-tag\xA0foo=\"bar\"]" ); // ISO-8859-1 no-break space not supported currently.
+		$this->assertNull( $this->tagname );
+		$this->assertNull( $this->atts );
+
+		$this->atts = $this->content = $this->tagname = null;
+		do_shortcode( "[test-shortcode-tag\xC2\xA0foo=\"bar\"]" );
+		$this->assertNull( $this->tagname );
+		$this->assertNull( $this->atts );
+
+		remove_filter( 'pre_option_blog_charset', array( $this, '_charset_iso_8859_1' ) );
+	}
+
+	/**
 	 * @ticket 14050
 	 */
 	function test_shortcode_unautop() {
@@ -638,6 +683,10 @@
 				'',
 				false,
 			),
+			array(
+				"bad\xc2\xa0space",
+				false,
+			),
 		);
 	}
 
@@ -655,6 +704,10 @@
 				'unreserved!#$%()*+,-.;?@^_{|}~chars',
 				true,
 			),
+			array(
+				"unreservedutf8\xc2\xa1\xe0\xa0\x80\xf0\xa0\x80\x80\xc2\xa2chars",
+				true,
+			),
 		);
 	}
 
Index: tests/qunit/wp-includes/js/shortcode.js
===================================================================
--- tests/qunit/wp-includes/js/shortcode.js	(revision 39198)
+++ tests/qunit/wp-includes/js/shortcode.js	(working copy)
@@ -96,6 +96,14 @@
 		equal( result, undefined, 'stub does not trigger match' );
 	});
 
+	test( 'next() should find the shortcode delimited by no-break space (U+00A0)', function() {
+		var result;
+
+
+		result = wp.shortcode.next( 'foo', 'this has a no-break space delimited [foo\u00a0param="foo"] shortcode' );
+		equal( result.index, 36, 'no-break space delimited foo shortcode found at index 36' );
+	});
+
 	test( 'replace() should replace the shortcode', function() {
 		var result;
 
