Index: src/wp-includes/kses.php
===================================================================
--- src/wp-includes/kses.php	(revision 40915)
+++ src/wp-includes/kses.php	(working copy)
@@ -827,20 +827,28 @@
 	if (preg_match('%\s*/\s*$%', $attr))
 		$xhtml_slash = ' /';
 
-	// Are any attributes allowed at all for this element?
-	if ( ! isset( $allowed_html[ strtolower( $element ) ] ) || true === $allowed_html[ strtolower( $element ) ] || count( $allowed_html[ strtolower( $element ) ] ) == 0 ) {
-		return "<$element$xhtml_slash>";
+	/**
+	 * Custom function per tag value. The callback takes ($element, $attr) as arguments.
+	 */
+	if ( is_callable( $allowed_html[strtolower($element)] ) ) {
+		$attr2 = call_user_func( $allowed_html[strtolower($element)], $element, $attr );
 	}
+	else {	
+		// Are any attributes allowed at all for this element?
+		if ( ! isset( $allowed_html[ strtolower( $element ) ] ) || true === $allowed_html[ strtolower( $element ) ] || count( $allowed_html[ strtolower( $element ) ] ) == 0 ) {
+			return "<$element$xhtml_slash>";
+		}
 
-	// Split it
-	$attrarr = wp_kses_hair($attr, $allowed_protocols);
+		// Split it
+		$attrarr = wp_kses_hair($attr, $allowed_protocols);
 
-	// Go through $attrarr, and save the allowed attributes for this element
-	// in $attr2
-	$attr2 = '';
-	foreach ( $attrarr as $arreach ) {
-		if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) {
-			$attr2 .= ' '.$arreach['whole'];
+		// Go through $attrarr, and save the allowed attributes for this element
+		// in $attr2
+		$attr2 = '';
+		foreach ( $attrarr as $arreach ) {
+			if ( wp_kses_attr_check( $arreach['name'], $arreach['value'], $arreach['whole'], $arreach['vless'], $element, $allowed_html ) ) {
+				$attr2 .= ' '.$arreach['whole'];
+			}
 		}
 	}
 
@@ -872,6 +880,18 @@
 		return false;
 	}
 
+	/**
+	 * Custom function per attribute value. The callback takes ($name, $value, $element) as arguments.
+	 */
+	if ( is_callable( $allowed_attr[$name_low] ) ) {
+		$new_value = call_user_func( $allowed_attr[$name_low], $name, $value, $element );
+		if ( empty( $new_value ) ) {
+			$name = $value = $whole = '';
+			return false;
+		}
+		return true;
+	}
+
 	if ( 'style' == $name_low ) {
 		$new_value = safecss_filter_attr( $value );
 
Index: tests/phpunit/tests/kses.php
===================================================================
--- tests/phpunit/tests/kses.php	(revision 40915)
+++ tests/phpunit/tests/kses.php	(working copy)
@@ -680,6 +680,37 @@
 	}
 
 	/**
+	 * @ticket 39724
+	 */
+	function test_wp_kses_attr_check_custom() {
+		add_filter( 'wp_kses_allowed_html', array( $this, '_wp_kses_allowed_html_custom_filter' ), 10, 2 );
+		
+		$input = '<span foo="bar" style="color: rgb(100,100,100);" enable data-test="foo">text</span>';
+		$this->assertEquals('<span style="color: rgb(100,100,100);" enable data-test="foo">text</span>', wp_kses( $input, '' ) );
+
+		$input = '<iframe src=""></iframe>';
+		$this->assertEquals('<iframe src="" disabled></iframe>', wp_kses( $input, '' ) );
+		
+		remove_filter( 'wp_kses_allowed_html', array( $this, '_wp_kses_allowed_html_custom_filter' ), 10, 2 );
+	}
+	
+	function _wp_kses_allowed_html_custom_filter ( $tags, $context ) {
+		// span attributes settings
+		$tags['span']['enable'] = 1;
+		$tags['span']['data-test'] = 1;
+		$tags['span']['style'] = function ( $name, $value, $element ) { 
+			return $value;
+		};
+
+		// iframe callback
+		$tags['iframe'] = function ( $element, $attr ) { 
+			return $attr . ' disabled';
+		};
+
+		return $tags;
+	}
+
+	/**
 	 * @ticket 40680
 	 */
 	function test_wp_kses_attr_no_attributes_allowed_with_empty_array() {
