Index: src/wp-includes/class-wp-post-type.php
===================================================================
--- src/wp-includes/class-wp-post-type.php	(revision 38853)
+++ src/wp-includes/class-wp-post-type.php	(working copy)
@@ -325,7 +325,7 @@
 	public $rewrite;
 
 	/**
-	 * The features supported by the post type.
+	 * The features supported by the post type, per post type definition.
 	 *
 	 * @since 4.6.0
 	 * @access public
@@ -334,6 +334,25 @@
 	public $supports;
 
 	/**
+	 * All features supported by the post type.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var array $features
+	 */
+	private $features = array();
+
+	/**
+	 * A list of early post type features.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @static
+	 * @var array
+	 */
+	private static $early_features = array();
+
+	/**
 	 * Constructor.
 	 *
 	 * Will populate object properties from the provided arguments and assign other
@@ -351,6 +370,11 @@
 	public function __construct( $post_type, $args = array() ) {
 		$this->name = $post_type;
 
+		if ( ! empty( self::$early_features[ $this->name ] ) ) {
+			$this->features = self::$early_features[ $this->name ];
+			unset( self::$early_features[ $this->name ] );
+		}
+
 		$this->set_props( $args );
 	}
 
@@ -511,11 +535,11 @@
 	 */
 	public function add_supports() {
 		if ( ! empty( $this->supports ) ) {
-			add_post_type_support( $this->name, $this->supports );
+			$this->add_support( $this->supports );
 			unset( $this->supports );
 		} elseif ( false !== $this->supports ) {
 			// Add default features.
-			add_post_type_support( $this->name, array( 'title', 'editor' ) );
+			$this->add_support( array( 'title', 'editor' ) );
 		}
 	}
 
@@ -602,17 +626,123 @@
 	}
 
 	/**
+	 * Registers support of certain features for the post type.
+	 *
+	 * All core features are directly associated with a functional area of the edit
+	 * screen, such as the editor or a meta box. Features include: 'title', 'editor',
+	 * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
+	 * 'thumbnail', 'custom-fields', and 'post-formats'.
+	 *
+	 * Additionally, the 'revisions' feature dictates whether the post type will
+	 * store revisions, and the 'comments' feature dictates whether the comments
+	 * count will show on the edit screen.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string|array $feature The feature being added, accepts an array of
+	 *                              feature strings or a single string.
+	 * @param bool         $value   Optional. The value to give to the feature. Default
+	 *                              true.
+	 * @param string       $subtype Optional. A subtype to add this feature to. An
+	 *                              empty value will add this feature to the overall
+	 *                              post type. Default empty.
+	 */
+	public function add_support( $feature, $value = true, $subtype = '' ) {
+		$features = (array) $feature;
+		foreach ( $features as $feature ) {
+			$this->features[ $subtype ][ $feature ] = $value;
+		}
+	}
+
+	/**
+	 * Removes support for a feature from the post type.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string $feature The feature being removed.
+	 * @param string $subtype Optional. A subtype to remove this feature from. An
+	 *                        empty value will remove this feature from the overall
+	 *                        post type. Default empty.
+	 */
+	public function remove_support( $feature, $subtype = '' ) {
+		unset( $this->features[ $subtype ][ $feature ] );
+	}
+
+	/**
+	 * Gets all the post type features.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string $subtype Optional. A subtype to get its features. An
+	 *                        empty value will return the overall post type
+	 *                        features. Default empty.
+	 * @return array Post type supports list.
+	 */
+	public function get_all_supports( $subtype = '' ) {
+		return $this->features[ $subtype ];
+	}
+
+	/**
+	 * Checks the post type's support for a given feature.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param string $feature The feature being checked.
+	 * @param string $subtype Optional. A subtype to check in its features. An
+	 *                        empty value will check within the overall post type
+	 *                        features. Default empty.
+	 * @return bool Whether the post type supports the given feature.
+	 */
+	public function supports( $feature, $subtype = '' ) {
+		return isset( $this->features[ $subtype ][ $feature ] );
+	}
+
+	/**
+	 * Retrieves a list of post type and subtype names that support a specific feature.
+	 *
+	 * If the features are supported by the overall post type, the post type name is
+	 * included in the list. If the features are supported by a specific subtype, a
+	 * string consisting of post type name and subtype name delimited by a colon is
+	 * included in the list.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param array|string $feature  Single feature or an array of features the post type should support.
+	 * @param string       $operator Optional. The logical operation to perform. 'or' means
+	 *                               only one element from the array needs to match; 'and'
+	 *                               means all elements must match; 'not' means no elements may
+	 *                               match. Default 'and'.
+	 * @return array A list of post type and subtype names.
+	 */
+	public function get_by_support( $feature, $operator = 'and' ) {
+		$features = array_fill_keys( (array) $feature, true );
+		$subtypes = array_keys( wp_filter_object_list( $this->features, $features, $operator ) );
+
+		$names = array();
+		foreach ( $subtypes as $subtype ) {
+			if ( empty( $subtype ) ) {
+				$names[] = $this->name;
+			} else {
+				$names[] = $this->name . ':' . $subtype;
+			}
+		}
+
+		return $names;
+	}
+
+	/**
 	 * Removes the features support for the post type.
 	 *
 	 * @since 4.6.0
 	 * @access public
-	 *
-	 * @global array $_wp_post_type_features Post type features.
 	 */
 	public function remove_supports() {
-		global $_wp_post_type_features;
-
-		unset( $_wp_post_type_features[ $this->name ] );
+		$this->features = array();
 	}
 
 	/**
@@ -683,4 +813,71 @@
 	public function remove_hooks() {
 		remove_action( 'future_' . $this->name, '_future_post_hook', 5 );
 	}
+
+	/**
+	 * Registers support of certain features for a post type early.
+	 *
+	 * The feature is added just-in-time when add_post_type_support() is called for
+	 * a $post_type before it has been registered.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 * @ignore
+	 *
+	 * @see add_post_type_support()
+	 *
+	 * @param string       $post_type The post type for which to add the feature.
+	 * @param string|array $feature   The feature being added, accepts an array of
+	 *                                feature strings or a single string.
+	 * @param bool         $value     Optional. The value to give to the feature. Default
+	 *                                true.
+	 * @param string       $subtype   Optional. A subtype to add this feature to. An
+	 *                                empty value will add this feature to the overall
+	 *                                post type. Default empty.
+	 */
+	public static function add_support_early( $post_type, $feature, $value, $subtype = '' ) {
+		self::$early_features[ $post_type ][ $subtype ][ $feature ] = $value;
+	}
+
+	/**
+	 * Removes support for a feature from a post type early.
+	 *
+	 * The feature is removed just-in-time when remove_post_type_support() is called for
+	 * a $post_type before it has been registered.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 * @ignore
+	 *
+	 * @see remove_post_type_support()
+	 *
+	 * @param string $post_type The post type for which to remove the feature.
+	 * @param string $feature   The feature being removed.
+	 * @param string $subtype   Optional. A subtype to remove this feature from. An
+	 *                          empty value will remove this feature from the overall
+	 *                          post type. Default empty.
+	 */
+	public static function remove_support_early( $post_type, $feature, $subtype = '' ) {
+		if ( ! isset( self::$early_features[ $post_type ][ $subtype ][ $feature ] ) ) {
+			return;
+		}
+
+		unset( self::$early_features[ $post_type ][ $subtype ][ $feature ] );
+	}
+
+	/**
+	 * Returns the list early post type features.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 * @ignore
+	 *
+	 * @return array List of early post type features.
+	 */
+	public static function get_early_support() {
+		return self::$early_features;
+	}
 }
Index: src/wp-includes/post.php
===================================================================
--- src/wp-includes/post.php	(revision 38853)
+++ src/wp-includes/post.php	(working copy)
@@ -1484,83 +1484,120 @@
  * count will show on the edit screen.
  *
  * @since 3.0.0
- *
- * @global array $_wp_post_type_features
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @param string       $post_type The post type for which to add the feature.
  * @param string|array $feature   The feature being added, accepts an array of
  *                                feature strings or a single string.
  */
 function add_post_type_support( $post_type, $feature ) {
-	global $_wp_post_type_features;
+	$subtype = '';
+	if ( strpos( $post_type, ':' ) ) {
+		list( $post_type, $subtype ) = explode( ':', $post_type );
+	}
+
+	if ( func_num_args() == 2 ) {
+		$value = true;
+	} else {
+		$value = array_slice( func_get_args(), 2 );
+	}
+
+	$ptype_obj = get_post_type_object( $post_type );
+	if ( ! $ptype_obj ) {
+		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %s is not registered. Post type features should only be added to existing post types.' ), $post_type ), '4.7.0' );
 
-	$features = (array) $feature;
-	foreach ($features as $feature) {
-		if ( func_num_args() == 2 )
-			$_wp_post_type_features[$post_type][$feature] = true;
-		else
-			$_wp_post_type_features[$post_type][$feature] = array_slice( func_get_args(), 2 );
+		WP_Post_Type::add_support_early( $post_type, $feature, $value, $subtype );
+		return;
 	}
+
+	$ptype_obj->add_support( $feature, $value, $subtype );
 }
 
 /**
  * Remove support for a feature from a post type.
  *
  * @since 3.0.0
- *
- * @global array $_wp_post_type_features
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @param string $post_type The post type for which to remove the feature.
  * @param string $feature   The feature being removed.
  */
 function remove_post_type_support( $post_type, $feature ) {
-	global $_wp_post_type_features;
+	$subtype = '';
+	if ( strpos( $post_type, ':' ) ) {
+		list( $post_type, $subtype ) = explode( ':', $post_type );
+	}
+
+	$ptype_obj = get_post_type_object( $post_type );
+	if ( ! $ptype_obj ) {
+		_doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %s is not registered. Post type features should only be removed from existing post types.' ), $post_type ), '4.7.0' );
 
-	unset( $_wp_post_type_features[ $post_type ][ $feature ] );
+		WP_Post_Type::remove_support_early( $post_type, $feature, $subtype );
+		return;
+	}
+
+	$ptype_obj->remove_support( $feature, $subtype );
 }
 
 /**
  * Get all the post type features
  *
  * @since 3.4.0
- *
- * @global array $_wp_post_type_features
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @param string $post_type The post type.
  * @return array Post type supports list.
  */
 function get_all_post_type_supports( $post_type ) {
-	global $_wp_post_type_features;
+	$subtype = '';
+	if ( strpos( $post_type, ':' ) ) {
+		list( $post_type, $subtype ) = explode( ':', $post_type );
+	}
 
-	if ( isset( $_wp_post_type_features[$post_type] ) )
-		return $_wp_post_type_features[$post_type];
+	$ptype_obj = get_post_type_object( $post_type );
+	if ( ! $ptype_obj ) {
+		$post_type_features = WP_Post_Type::get_early_support();
+
+		if ( isset( $post_type_features[ $post_type ][ $subtype ] ) ) {
+			return $post_type_features[ $post_type ][ $subtype ];
+		}
+		return array();
+	}
 
-	return array();
+	return $ptype_obj->get_all_supports( $subtype );
 }
 
 /**
  * Check a post type's support for a given feature.
  *
  * @since 3.0.0
- *
- * @global array $_wp_post_type_features
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @param string $post_type The post type being checked.
  * @param string $feature   The feature being checked.
  * @return bool Whether the post type supports the given feature.
  */
 function post_type_supports( $post_type, $feature ) {
-	global $_wp_post_type_features;
+	$subtype = '';
+	if ( strpos( $post_type, ':' ) ) {
+		list( $post_type, $subtype ) = explode( ':', $post_type );
+	}
+
+	$ptype_obj = get_post_type_object( $post_type );
+	if ( ! $ptype_obj ) {
+		$post_type_features = WP_Post_Type::get_early_support();
 
-	return ( isset( $_wp_post_type_features[$post_type][$feature] ) );
+		return isset( $post_type_features[ $post_type ][ $subtype ][ $feature ] );
+	}
+
+	return $ptype_obj->supports( $feature, $subtype );
 }
 
 /**
  * Retrieves a list of post type names that support a specific feature.
  *
  * @since 4.5.0
- *
- * @global array $_wp_post_type_features Post type features
+ * @since 4.7.0 Uses `WP_Post_Type` to handle features.
  *
  * @param array|string $feature  Single feature or an array of features the post types should support.
  * @param string       $operator Optional. The logical operation to perform. 'or' means
@@ -1570,11 +1607,30 @@
  * @return array A list of post type names.
  */
 function get_post_types_by_support( $feature, $operator = 'and' ) {
-	global $_wp_post_type_features;
+	$names = array();
 
-	$features = array_fill_keys( (array) $feature, true );
+	$ptype_objects = get_post_types( array(), 'objects' );
+	foreach ( $ptype_objects as $ptype_obj ) {
+		$names = array_merge( $names, $ptype_obj->get_by_support( $feature, $operator ) );
+	}
+
+	$post_type_features = WP_Post_Type::get_early_support();
+	if ( ! empty( $post_type_features ) ) {
+		$features = array_fill_keys( (array) $feature, true );
+
+		foreach ( $post_type_features as $post_type => $subtypes ) {
+			$subtypes = array_keys( wp_filter_object_list( $subtypes, $features, $operator ) );
+			foreach ( $subtypes as $subtype ) {
+				if ( empty( $subtype ) ) {
+					$names[] = $post_type;
+				} else {
+					$names[] = $post_type . ':' . $subtype;
+				}
+			}
+		}
+	}
 
-	return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
+	return $names;
 }
 
 /**
Index: tests/phpunit/tests/post/typeSupport.php
===================================================================
--- tests/phpunit/tests/post/typeSupport.php	(revision 0)
+++ tests/phpunit/tests/post/typeSupport.php	(working copy)
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @group post
+ */
+class Tests_Post_Type_Support extends WP_UnitTestCase {
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_add_post_type_support_basic() {
+		$post_type = rand_str( 20 );
+		$feature = 'custom_feature_name';
+
+		register_post_type( $post_type );
+		add_post_type_support( $post_type, $feature );
+		$this->assertTrue( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 * @expectedIncorrectUsage add_post_type_support
+	 */
+	public function test_add_post_type_support_unregistered_post_type() {
+		$post_type = rand_str( 20 );
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		$this->assertTrue( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 * @expectedIncorrectUsage add_post_type_support
+	 */
+	public function test_add_post_type_support_before_register_post_type_after_registration() {
+		$post_type = rand_str( 20 );
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		register_post_type( $post_type );
+		$this->assertTrue( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_remove_post_type_support_basic() {
+		$post_type = rand_str( 20 );
+		$feature = 'editor';
+
+		register_post_type( $post_type );
+		remove_post_type_support( $post_type, $feature );
+		$this->assertFalse( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 * @expectedIncorrectUsage add_post_type_support
+	 * @expectedIncorrectUsage remove_post_type_support
+	 */
+	public function test_remove_post_type_support_unregistered_post_type() {
+		$post_type = rand_str( 20 );
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		remove_post_type_support( $post_type, $feature );
+		$this->assertFalse( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 * @expectedIncorrectUsage remove_post_type_support
+	 */
+	public function test_remove_post_type_support_before_register_post_type() {
+		$post_type = rand_str( 20 );
+		$default_feature = 'editor';
+
+		// Features are added on post type registration, so removing them before shouldn't matter.
+		remove_post_type_support( $post_type, $default_feature );
+		register_post_type( $post_type );
+		$this->assertTrue( post_type_supports( $post_type, $default_feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 * @expectedIncorrectUsage add_post_type_support
+	 */
+	public function test_get_post_types_by_support_mixed() {
+		$registered_post_type = rand_str( 20 );
+		$unregistered_post_type = rand_str( 20 );
+		$feature = 'unique_feature_name';
+
+		register_post_type( $registered_post_type );
+
+		add_post_type_support( $registered_post_type, $feature );
+		add_post_type_support( $unregistered_post_type, $feature );
+		$this->assertEqualSets( array( $registered_post_type, $unregistered_post_type ), get_post_types_by_support( $feature ) );
+	}
+}

Property changes on: tests/phpunit/tests/post/typeSupport.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: tests/phpunit/tests/post/wpPostType.php
===================================================================
--- tests/phpunit/tests/post/wpPostType.php	(revision 38853)
+++ tests/phpunit/tests/post/wpPostType.php	(working copy)
@@ -148,4 +148,146 @@
 		$this->assertEqualSets( array( 'post_tag' ), $taxonomies );
 		$this->assertEqualSets( array(), $taxonomies_after );
 	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_add_support_basic() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$feature = 'custom_feature_name';
+
+		$post_type_object->add_support( $feature );
+		$this->assertTrue( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_add_support_with_subtype() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype = rand_str();
+		$feature = 'custom_feature_name';
+
+		$post_type_object->add_support( $feature, true, $subtype );
+		$this->assertTrue( post_type_supports( $post_type . ':' . $subtype, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_remove_support_basic() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$feature = 'editor';
+
+		$post_type_object->remove_support( $feature );
+		$this->assertFalse( post_type_supports( $post_type, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_remove_support_with_subtype() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype = rand_str();
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type . ':' . $subtype, $feature );
+		$post_type_object->remove_support( $feature, $subtype );
+		$this->assertFalse( post_type_supports( $post_type . ':' . $subtype, $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_get_all_supports_basic() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		$this->assertEqualSets( array(
+			'title' => true,
+			'editor' => true,
+			$feature => true,
+		), $post_type_object->get_all_supports() );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_get_all_supports_with_subtype() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype = rand_str();
+		$features = array( 'custom_feature_name', 'another_feature_name' );
+
+		add_post_type_support( $post_type . ':' . $subtype, $features );
+		$this->assertEqualSets( array_fill_keys( $features, true ), $post_type_object->get_all_supports( $subtype ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_supports_basic() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		$this->assertTrue( $post_type_object->supports( $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_supports_with_subtype() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype = rand_str();
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type . ':' . $subtype, $feature );
+		$this->assertTrue( $post_type_object->supports( $feature, $subtype ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_get_by_support() {
+		$post_type = rand_str( 20 );
+		$post_type_object = register_post_type( $post_type );
+
+		$subtype1 = rand_str();
+		$subtype2 = rand_str();
+		$feature = 'custom_feature_name';
+
+		add_post_type_support( $post_type, $feature );
+		add_post_type_support( $post_type . ':' . $subtype2, $feature );
+		$this->assertEqualSets( array( $post_type, $post_type . ':' . $subtype2 ), $post_type_object->get_by_support( $feature ) );
+	}
+
+	/**
+	 * @ticket 37667
+	 */
+	public function test_migrate_support_from_global() {
+		$post_type = rand_str();
+
+		$feature = 'custom_feature_name';
+
+		WP_Post_Type::add_support_early( $post_type, $feature, true );
+		$post_type_object = new WP_Post_Type( $post_type );
+		$this->assertTrue( $post_type_object->supports( $feature ) );
+	}
 }
