Index: src/wp-includes/class.wp-scripts.php
===================================================================
--- src/wp-includes/class.wp-scripts.php	(revision 36612)
+++ src/wp-includes/class.wp-scripts.php	(working copy)
@@ -195,7 +195,8 @@
 		if ( ! $src )
 			return true;
 
-		$tag = "{$cond_before}<script type='text/javascript' src='$src'></script>\n{$cond_after}";
+		$attributes = $this->get_concatenated_script_attributes( $handle, $src );
+		$tag = "{$cond_before}<script$attributes></script>\n{$cond_after}";
 
 		/**
 		 * Filter the HTML script tag of an enqueued script.
@@ -218,6 +219,65 @@
 	}
 
 	/**
+	 * Get attributes for the script tag.
+	 *
+	 * @since  4.0.0
+	 *
+	 * @param  string $handle Script registered handle.
+	 * @param  string $src    Script registered src.
+	 * @return array Attribues.
+	 */
+	public function get_script_attributes( $handle, $src ) {
+		$default_attributes = array(
+			'type' => 'text/javascript',
+			'src'  => $src
+		);
+
+		$attributes = isset( $this->registered[ $handle ]->args['attributes'] )
+			? (array) $this->registered[ $handle ]->args['attributes']
+			: array();
+
+		$attributes = wp_parse_args( $attributes, $default_attributes );
+		return $attributes;
+	}
+
+
+	/**
+	 * Get Concatenated element attributes for the script tag.
+	 *
+	 * @since 4.5.0
+	 *
+	 * @param  string $handle Script registered handle
+	 * @param  string $src    Script registered src
+	 * @return string Concatenated attributes string
+	*/
+	public function get_concatenated_script_attributes( $handle, $src ) {
+		$attributes = $this->get_script_attributes( $handle, $src );
+
+		/**
+		* Filter the script loader attributes.
+		*
+		* @since 4.5.0
+		*
+		* @param array  $attributes Array of script tag attributes.
+		* @param string $handle     Script handle.
+		* @param string $src        Script loader source path.
+		*/
+		$attributes = apply_filters( 'script_loader_attributes', $attributes, $handle, $src );
+		// Ensure source is set.
+		$attributes['src'] = isset( $attributes['src'] ) ? $attributes['src'] : $src;
+
+		$concat_attributes = '';
+		foreach ( $attributes as $attribute => $attribute_value ) {
+			if ( ! is_null( $attribute ) && ! is_null( $attribute_value ) ) {
+				$concat_attributes .=  sprintf( " %s='%s'", esc_attr( $attribute ), esc_attr( $attribute_value )  );
+			}
+		}
+
+		return $concat_attributes;
+	}
+
+	/**
 	 * Localizes a script, only if the script has already been added
 	 *
 	 * @param string $handle
Index: src/wp-includes/functions.wp-scripts.php
===================================================================
--- src/wp-includes/functions.wp-scripts.php	(revision 36612)
+++ src/wp-includes/functions.wp-scripts.php	(working copy)
@@ -95,6 +95,7 @@
  *
  * @since 2.6.0
  * @since 4.3.0 A return value was added.
+ * @since 4.5.0 The sixth argument was added.
  *
  * @param string      $handle    Name of the script. Should be unique.
  * @param string      $src       Path to the script from the WordPress root directory. Example: '/js/myscript.js'.
@@ -106,13 +107,21 @@
  *                               If set to null, no version is added. Default 'false'. Accepts 'false', 'null', or 'string'.
  * @param bool        $in_footer Optional. Whether to enqueue the script before </head> or before </body>.
  *                               Default 'false'. Accepts 'false' or 'true'.
+ * @param array       $args {
+ *     Optional script arguments.
+ *
+ *     @type array $attributes Array of script tag attributes.
+ *                             Default: array( 'type' => 'text/javascript' )
+ * }
  * @return bool Whether the script has been registered. True on success, false on failure.
  */
-function wp_register_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false ) {
+function wp_register_script( $handle, $src, $deps = array(), $ver = false, $in_footer = false, $attributes = array() ) {
 	$wp_scripts = wp_scripts();
 	_wp_scripts_maybe_doing_it_wrong( __FUNCTION__ );
 
-	$registered = $wp_scripts->add( $handle, $src, $deps, $ver );
+	$attributes = wp_parse_args( $attributes, array( 'type' => 'text/javascript' ) );
+
+	$registered = $wp_scripts->add( $handle, $src, $deps, $ver, array( 'attributes' => $attributes ) );
 	if ( $in_footer ) {
 		$wp_scripts->add_data( $handle, 'group', 1 );
 	}
@@ -211,6 +220,7 @@
  * @see WP_Dependencies::enqueue()
  *
  * @since 2.6.0
+ * @since 4.5.0 The sixth argument was added.
  *
  * @param string      $handle    Name of the script.
  * @param string|bool $src       Path to the script from the root directory of WordPress. Example: '/js/myscript.js'.
@@ -220,18 +230,25 @@
  *                               and so should be included if a version number is available and makes sense for the script.
  * @param bool        $in_footer Optional. Whether to enqueue the script before </head> or before </body>.
  *                               Default 'false'. Accepts 'false' or 'true'.
+ * @param array       $args {
+ *     Optional script arguments.
+ *
+ *     @type array $attributes Array of script tag attributes.
+ *                             Default: array( 'type' => 'text/javascript' )
+ * }
  */
-function wp_enqueue_script( $handle, $src = false, $deps = array(), $ver = false, $in_footer = false ) {
+function wp_enqueue_script( $handle, $src = false, $deps = array(), $ver = false, $in_footer = false, $attributes = array() ) {
 	$wp_scripts = wp_scripts();
 
 	_wp_scripts_maybe_doing_it_wrong( __FUNCTION__ );
 
+	$attributes = wp_parse_args( $attributes, array( 'type' => 'text/javascript' ) );
 
 	if ( $src || $in_footer ) {
 		$_handle = explode( '?', $handle );
 
 		if ( $src ) {
-			$wp_scripts->add( $_handle[0], $src, $deps, $ver );
+			$wp_scripts->add( $_handle[0], $src, $deps, $ver, array( 'attributes' => $attributes ) );
 		}
 
 		if ( $in_footer ) {
Index: tests/phpunit/tests/dependencies/getConcatenatedScriptAttributes.php
===================================================================
--- tests/phpunit/tests/dependencies/getConcatenatedScriptAttributes.php	(nonexistent)
+++ tests/phpunit/tests/dependencies/getConcatenatedScriptAttributes.php	(working copy)
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @group dependencies
+ * @group scripts
+ */
+class Tests_Dependencies_GetConcatenatedScriptAttributes extends WP_UnitTestCase {
+	var $old_wp_scripts;
+
+	function setUp() {
+		parent::setUp();
+		$this->old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null;
+		remove_action( 'wp_default_scripts', 'wp_default_scripts' );
+		$GLOBALS['wp_scripts'] = new WP_Scripts();
+		$GLOBALS['wp_scripts']->default_version = get_bloginfo( 'version' );
+	}
+
+	function tearDown() {
+		$GLOBALS['wp_scripts'] = $this->old_wp_scripts;
+		add_action( 'wp_default_scripts', 'wp_default_scripts' );
+		parent::tearDown();
+	}
+
+	/**
+	 * @ticket 22249
+	 */
+	function test_get_concatenated_script_attributes_with_defaults() {
+		global $wp_scripts;
+		wp_register_script( 'cool-script', '/cool-script.js', array(), '1' );
+
+		$actual = $wp_scripts->get_concatenated_script_attributes( 'cool-script', '/cool-script.js' );
+		$expected = " type='text/javascript' src='/cool-script.js'";
+		$this->assertEquals( $expected, $actual );
+	}
+
+	/**
+	 * @ticket 22249
+	 */
+	function test_get_concatenated_script_attributes_with_arbitrary_attributes_added() {
+		global $wp_scripts;
+		wp_register_script( 'cool-script', '/cool-script.js', array(), '1', false, array( 'async' => 'async' ) );
+
+		$actual = $wp_scripts->get_concatenated_script_attributes( 'cool-script', '/cool-script.js' );
+		$expected = " type='text/javascript' src='/cool-script.js' async='async'";
+		$this->assertEquals( $expected, $actual );
+	}
+
+}
Index: tests/phpunit/tests/dependencies/getScriptAttributes.php
===================================================================
--- tests/phpunit/tests/dependencies/getScriptAttributes.php	(nonexistent)
+++ tests/phpunit/tests/dependencies/getScriptAttributes.php	(working copy)
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @group dependencies
+ * @group scripts
+ */
+class Tests_Dependencies_GetScriptAttributes extends WP_UnitTestCase {
+	var $old_wp_scripts;
+
+	function setUp() {
+		parent::setUp();
+		$this->old_wp_scripts = isset( $GLOBALS['wp_scripts'] ) ? $GLOBALS['wp_scripts'] : null;
+		remove_action( 'wp_default_scripts', 'wp_default_scripts' );
+		$GLOBALS['wp_scripts'] = new WP_Scripts();
+		$GLOBALS['wp_scripts']->default_version = get_bloginfo( 'version' );
+	}
+
+	function tearDown() {
+		$GLOBALS['wp_scripts'] = $this->old_wp_scripts;
+		add_action( 'wp_default_scripts', 'wp_default_scripts' );
+		parent::tearDown();
+	}
+
+	/**
+	 * @ticket 22249
+	 */
+	function test_get_script_attributes_with_defaults() {
+		global $wp_scripts;
+		wp_register_script( 'cool-script', '/cool-script.js' );
+
+		$expected_attributes = array(
+			'type' => 'text/javascript',
+			'src' => '/cool-script.js',
+		);
+		$actual_attributes = $wp_scripts->get_script_attributes( 'cool-script', '/cool-script.js' );
+		$this->assertEquals( $expected_attributes, $actual_attributes );
+	}
+
+	/**
+	 * @ticket 22249
+	 */
+	function test_get_script_attributes_with_arbitrary_attributes_added() {
+		global $wp_scripts;
+		wp_register_script( 'cool-script', '/cool-script.js', array(), '1', false, array( 'async' => 'async' ) );
+
+		$expected_attributes = array(
+			'type' => 'text/javascript',
+			'src' => '/cool-script.js',
+			'async' => 'async',
+		);
+		$actual_attributes = $wp_scripts->get_script_attributes( 'cool-script', '/cool-script.js' );
+		$this->assertEquals( $expected_attributes, $actual_attributes );
+	}
+
+}
Index: tests/phpunit/tests/dependencies/scripts.php
===================================================================
--- tests/phpunit/tests/dependencies/scripts.php	(revision 36612)
+++ tests/phpunit/tests/dependencies/scripts.php	(working copy)
@@ -266,4 +266,31 @@
 		$this->assertEquals( $expected_header, $header );
 		$this->assertEquals( $expected_footer, $footer );
 	}
+
+	/**
+	 * @ticket 22249
+	 */
+	function test_wp_register_script_should_allow_arbitrary_element_attributes() {
+		wp_register_script( 'cool-script', '/cool-script.js', array(), '1', false, array( 'async' => 'async' ) );
+
+		wp_enqueue_script( 'cool-script' );
+
+		$header = get_echo( 'wp_print_head_scripts' );
+		$expected_header  = "<script type='text/javascript' src='/cool-script.js?ver=1' async='async'></script>\n";
+
+		$this->assertEquals( $expected_header, $header );
+	}
+
+	/**
+	 * @ticket 22249
+	 */
+	function test_wp_enqueue_script_should_allow_arbitrary_element_attributes() {
+		wp_enqueue_script( 'cool-script', '/cool-script.js', array(), '1', false, array( 'async' => 'async' ) );
+
+		$header = get_echo( 'wp_print_head_scripts' );
+		$expected_header  = "<script type='text/javascript' src='/cool-script.js?ver=1' async='async'></script>\n";
+
+		$this->assertEquals( $expected_header, $header );
+	}
+
 }
