diff --git a/src/wp-includes/class.wp-scripts.php b/src/wp-includes/class.wp-scripts.php
index 42422d3db4..b5bb0228d5 100644
--- a/src/wp-includes/class.wp-scripts.php
+++ b/src/wp-includes/class.wp-scripts.php
@@ -149,6 +149,38 @@ public function init() {
 	}
 
 	/**
+	 * Get the expanded source URL for a script.
+	 *
+	 * @since 4.9.2
+	 *
+	 * @param  string $handle The script's handle.
+	 * @return string The URL.
+	 */
+	public function get_script_src( $handle ) {
+		$obj = $this->registered[ $handle ];
+		$src = $obj->src;
+		if ( null === $obj->ver ) {
+			$ver = '';
+		} else {
+			$ver = $obj->ver ? $obj->ver : $this->default_version;
+		}
+
+		// Add any extra arguments to the version string.
+		if ( isset($this->args[ $handle ]) )
+			$ver = $ver ? $ver . '&amp;' . $this->args[ $handle ] : $this->args[ $handle ];
+
+		if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && 0 === strpos( $src, $this->content_url ) ) ) {
+			$src = $this->base_url . $src;
+		}
+
+		if ( ! empty( $ver ) )
+			$src = add_query_arg( 'ver', $ver, $src );
+
+		/** This filter is documented in wp-includes/class.wp-scripts.php */
+		return apply_filters( 'script_loader_src', $src, $handle );
+	}
+
+		/**
 	 * Prints scripts.
 	 *
 	 * Prints the scripts passed to it or the print queue. Also prints all necessary dependencies.
@@ -330,8 +362,9 @@ public function do_item( $handle, $group = false ) {
 		if ( ! $src ) {
 			return true;
 		}
-
-		$tag = "{$cond_before}{$before_handle}<script type='text/javascript' src='$src'></script>\n{$after_handle}{$cond_after}";
+		
+		$attributes = $this->get_script_attributes_html( $handle );
+		$tag = "{$cond_before}{$before_handle}<script$attributes></script>\n{$after_handle}{$cond_after}";
 
 		/**
 		 * Filters the HTML script tag of an enqueued script.
@@ -407,6 +440,64 @@ public function print_inline_script( $handle, $position = 'after', $echo = true
 		return $output;
 	}
 
+	/**
+	 * Get the HTML element attributes for a script.
+	 *
+	 * Does not include the `src` attribute.
+	 *
+	 * @since 4.9.2
+	 *
+	 * @param string $handle Script registered handle.
+	 * @return array Attribues.
+	 */
+	public function get_script_attributes( $handle ) {
+		$default_attributes = array(
+			'type' => 'text/javascript',
+			'src' => $this->get_script_src( $handle ),
+		);
+
+		if ( isset( $this->registered[ $handle ]->args['attributes'] ) ) {
+			$extra_attributes = $this->registered[ $handle ]->args['attributes'];
+			$attributes = wp_parse_args( $extra_attributes, $default_attributes );
+		} else {
+			$attributes = $default_attributes;
+		}
+
+		/**
+		 * Filter the HTML element attributes for a script.
+		 *
+		 * @since 4.6.0
+		 *
+		 * @param array  $attributes Array of script element attributes.
+		 * @param string $handle     Script handle.
+		 */
+		$attributes = apply_filters( 'script_additional_attributes', $attributes, $handle );
+		return $attributes;
+	}
+
+
+	/**
+	 * Get the concatenated HTML element attributes for a script.
+	 *
+	 * @since 4.9.2
+	 *
+	 * @param  string $handle The script handle.
+	 * @return string Concatenated attributes.
+	 */
+	public function get_script_attributes_html( $handle ) {
+		$attributes = $this->get_script_attributes( $handle );
+		$html = '';
+		foreach ( $attributes as $attribute => $attribute_value ) {
+			if ( 'src' === $attribute ) {
+				$escaped_attribute_value = esc_url_raw( $attribute_value );
+			} else {
+				$escaped_attribute_value = esc_attr( $attribute_value );
+			}
+			$html .=  sprintf( " %s='%s'", esc_attr_name( $attribute ), $escaped_attribute_value );
+		}
+		return $html;
+	}
+
 	/**
 	 * Localizes a script, only if the script has already been added.
 	 *
diff --git a/src/wp-includes/class.wp-scripts.php.orig b/src/wp-includes/class.wp-scripts.php.orig
new file mode 100644
index 0000000000..81634ece41
--- /dev/null
+++ b/src/wp-includes/class.wp-scripts.php.orig
@@ -0,0 +1,587 @@
+<?php
+/**
+ * Dependencies API: WP_Scripts class
+ *
+ * @since 2.6.0
+ *
+ * @package WordPress
+ * @subpackage Dependencies
+ */
+
+/**
+ * Core class used to register scripts.
+ *
+ * @package WordPress
+ * @uses WP_Dependencies
+ * @since 2.1.0
+ */
+class WP_Scripts extends WP_Dependencies {
+	/**
+	 * Base URL for scripts.
+	 *
+	 * Full URL with trailing slash.
+	 *
+	 * @since 2.6.0
+	 * @access public
+	 * @var string
+	 */
+	public $base_url;
+
+	/**
+	 * URL of the content directory.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var string
+	 */
+	public $content_url;
+
+	/**
+	 * Default version string for stylesheets.
+	 *
+	 * @since 2.6.0
+	 * @access public
+	 * @var string
+	 */
+	public $default_version;
+
+	/**
+	 * Holds handles of scripts which are enqueued in footer.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var array
+	 */
+	public $in_footer = array();
+
+	/**
+	 * Holds a list of script handles which will be concatenated.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var string
+	 */
+	public $concat = '';
+
+	/**
+	 * Holds a string which contains script handles and their version.
+	 *
+	 * @since 2.8.0
+	 * @deprecated 3.4.0
+	 * @access public
+	 * @var string
+	 */
+	public $concat_version = '';
+
+	/**
+	 * Whether to perform concatenation.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var bool
+	 */
+	public $do_concat = false;
+
+	/**
+	 * Holds HTML markup of scripts and additional data if concatenation
+	 * is enabled.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var string
+	 */
+	public $print_html = '';
+
+	/**
+	 * Holds inline code if concatenation is enabled.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var string
+	 */
+	public $print_code = '';
+
+	/**
+	 * Holds a list of script handles which are not in the default directory
+	 * if concatenation is enabled.
+	 *
+	 * Unused in core.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var string
+	 */
+	public $ext_handles = '';
+
+	/**
+	 * Holds a string which contains handles and versions of scripts which
+	 * are not in the default directory if concatenation is enabled.
+	 *
+	 * Unused in core.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var string
+	 */
+	public $ext_version = '';
+
+	/**
+	 * List of default directories.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 * @var array
+	 */
+	public $default_dirs;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 2.6.0
+	 * @access public
+	 */
+	public function __construct() {
+		$this->init();
+		add_action( 'init', array( $this, 'init' ), 0 );
+	}
+
+	/**
+	 * Initialize the class.
+	 *
+	 * @since 3.4.0
+	 * @access public
+	 */
+	public function init() {
+		/**
+		 * Fires when the WP_Scripts instance is initialized.
+		 *
+		 * @since 2.6.0
+		 *
+		 * @param WP_Scripts &$this WP_Scripts instance, passed by reference.
+		 */
+		do_action_ref_array( 'wp_default_scripts', array(&$this) );
+	}
+
+	/**
+	 * Prints scripts.
+	 *
+	 * Prints the scripts passed to it or the print queue. Also prints all necessary dependencies.
+	 *
+	 * @since 2.1.0
+	 * @since 2.8.0 Added the `$group` parameter.
+	 * @access public
+	 *
+	 * @param mixed $handles Optional. Scripts to be printed. (void) prints queue, (string) prints
+	 *                       that script, (array of strings) prints those scripts. Default false.
+	 * @param int   $group   Optional. If scripts were queued in groups prints this group number.
+	 *                       Default false.
+	 * @return array Scripts that have been printed.
+	 */
+	public function print_scripts( $handles = false, $group = false ) {
+		return $this->do_items( $handles, $group );
+	}
+
+	/**
+	 * Prints extra scripts of a registered script.
+	 *
+	 * @since 2.1.0
+	 * @since 2.8.0 Added the `$echo` parameter.
+	 * @deprecated 3.3.0
+	 * @access public
+	 *
+	 * @see print_extra_script()
+	 *
+	 * @param string $handle The script's registered handle.
+	 * @param bool   $echo   Optional. Whether to echo the extra script instead of just returning it.
+	 *                       Default true.
+	 * @return bool|string|void Void if no data exists, extra scripts if `$echo` is true, true otherwise.
+	 */
+	public function print_scripts_l10n( $handle, $echo = true ) {
+		_deprecated_function( __FUNCTION__, '3.3.0', 'print_extra_script()' );
+		return $this->print_extra_script( $handle, $echo );
+	}
+
+	/**
+	 * Prints extra scripts of a registered script.
+	 *
+	 * @since 3.3.0
+	 * @access public
+	 *
+	 * @param string $handle The script's registered handle.
+	 * @param bool   $echo   Optional. Whether to echo the extra script instead of just returning it.
+	 *                       Default true.
+	 * @return bool|string|void Void if no data exists, extra scripts if `$echo` is true, true otherwise.
+	 */
+	public function print_extra_script( $handle, $echo = true ) {
+		if ( !$output = $this->get_data( $handle, 'data' ) )
+			return;
+
+		if ( !$echo )
+			return $output;
+
+		echo "<script type='text/javascript'>\n"; // CDATA and type='text/javascript' is not needed for HTML 5
+		echo "/* <![CDATA[ */\n";
+		echo "$output\n";
+		echo "/* ]]> */\n";
+		echo "</script>\n";
+
+		return true;
+	}
+
+	/**
+	 * Processes a script dependency.
+	 *
+	 * @since 2.6.0
+	 * @since 2.8.0 Added the `$group` parameter.
+	 * @access public
+	 *
+	 * @see WP_Dependencies::do_item()
+	 *
+	 * @param string $handle    The script's registered handle.
+	 * @param int|false $group  Optional. Group level: (int) level, (false) no groups. Default false.
+	 * @return bool True on success, false on failure.
+	 */
+	public function do_item( $handle, $group = false ) {
+		if ( !parent::do_item($handle) )
+			return false;
+
+		if ( 0 === $group && $this->groups[$handle] > 0 ) {
+			$this->in_footer[] = $handle;
+			return false;
+		}
+
+		if ( false === $group && in_array($handle, $this->in_footer, true) )
+			$this->in_footer = array_diff( $this->in_footer, (array) $handle );
+
+		$obj = $this->registered[$handle];
+
+		if ( null === $obj->ver ) {
+			$ver = '';
+		} else {
+			$ver = $obj->ver ? $obj->ver : $this->default_version;
+		}
+
+		if ( isset($this->args[$handle]) )
+			$ver = $ver ? $ver . '&amp;' . $this->args[$handle] : $this->args[$handle];
+
+		$src = $obj->src;
+		$cond_before = $cond_after = '';
+		$conditional = isset( $obj->extra['conditional'] ) ? $obj->extra['conditional'] : '';
+
+		if ( $conditional ) {
+			$cond_before = "<!--[if {$conditional}]>\n";
+			$cond_after = "<![endif]-->\n";
+		}
+
+		$before_handle = $this->print_inline_script( $handle, 'before', false );
+		$after_handle = $this->print_inline_script( $handle, 'after', false );
+
+		if ( $before_handle ) {
+			$before_handle = sprintf( "<script type='text/javascript'>\n%s\n</script>\n", $before_handle );
+		}
+
+		if ( $after_handle ) {
+			$after_handle = sprintf( "<script type='text/javascript'>\n%s\n</script>\n", $after_handle );
+		}
+
+		if ( $this->do_concat ) {
+			/**
+			 * Filters the script loader source.
+			 *
+			 * @since 2.2.0
+			 *
+			 * @param string $src    Script loader source path.
+			 * @param string $handle Script handle.
+			 */
+			$srce = apply_filters( 'script_loader_src', $src, $handle );
+
+			if ( $this->in_default_dir( $srce ) && ( $before_handle || $after_handle ) ) {
+				$this->do_concat = false;
+
+				// Have to print the so-far concatenated scripts right away to maintain the right order.
+				_print_scripts();
+				$this->reset();
+			} elseif ( $this->in_default_dir( $srce ) && ! $conditional ) {
+				$this->print_code .= $this->print_extra_script( $handle, false );
+				$this->concat .= "$handle,";
+				$this->concat_version .= "$handle$ver";
+				return true;
+			} else {
+				$this->ext_handles .= "$handle,";
+				$this->ext_version .= "$handle$ver";
+			}
+		}
+
+		$has_conditional_data = $conditional && $this->get_data( $handle, 'data' );
+
+		if ( $has_conditional_data ) {
+			echo $cond_before;
+		}
+
+		$this->print_extra_script( $handle );
+
+		if ( $has_conditional_data ) {
+			echo $cond_after;
+		}
+
+		// A single item may alias a set of items, by having dependencies, but no source.
+		if ( ! $obj->src ) {
+			return true;
+		}
+
+		if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && 0 === strpos( $src, $this->content_url ) ) ) {
+			$src = $this->base_url . $src;
+		}
+
+		if ( ! empty( $ver ) )
+			$src = add_query_arg( 'ver', $ver, $src );
+
+		/** This filter is documented in wp-includes/class.wp-scripts.php */
+		$src = esc_url( apply_filters( 'script_loader_src', $src, $handle ) );
+
+		if ( ! $src )
+			return true;
+
+		$tag = "{$cond_before}{$before_handle}<script type='text/javascript' src='$src'></script>\n{$after_handle}{$cond_after}";
+
+		/**
+		 * Filters the HTML script tag of an enqueued script.
+		 *
+		 * @since 4.1.0
+		 *
+		 * @param string $tag    The `<script>` tag for the enqueued script.
+		 * @param string $handle The script's registered handle.
+		 * @param string $src    The script's source URL.
+		 */
+		$tag = apply_filters( 'script_loader_tag', $tag, $handle, $src );
+
+		if ( $this->do_concat ) {
+			$this->print_html .= $tag;
+		} else {
+			echo $tag;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Adds extra code to a registered script.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 *
+	 * @param string $handle   Name of the script to add the inline script to. Must be lowercase.
+	 * @param string $data     String containing the javascript to be added.
+	 * @param string $position Optional. Whether to add the inline script before the handle
+	 *                         or after. Default 'after'.
+	 * @return bool True on success, false on failure.
+	 */
+	public function add_inline_script( $handle, $data, $position = 'after' ) {
+		if ( ! $data ) {
+			return false;
+		}
+
+		if ( 'after' !== $position ) {
+			$position = 'before';
+		}
+
+		$script   = (array) $this->get_data( $handle, $position );
+		$script[] = $data;
+
+		return $this->add_data( $handle, $position, $script );
+	}
+
+	/**
+	 * Prints inline scripts registered for a specific handle.
+	 *
+	 * @since 4.5.0
+	 * @access public
+	 *
+	 * @param string $handle   Name of the script to add the inline script to. Must be lowercase.
+	 * @param string $position Optional. Whether to add the inline script before the handle
+	 *                         or after. Default 'after'.
+	 * @param bool $echo       Optional. Whether to echo the script instead of just returning it.
+	 *                         Default true.
+	 * @return string|false Script on success, false otherwise.
+	 */
+	public function print_inline_script( $handle, $position = 'after', $echo = true ) {
+		$output = $this->get_data( $handle, $position );
+
+		if ( empty( $output ) ) {
+			return false;
+		}
+
+		$output = trim( implode( "\n", $output ), "\n" );
+
+		if ( $echo ) {
+			printf( "<script type='text/javascript'>\n%s\n</script>\n", $output );
+		}
+
+		return $output;
+	}
+
+	/**
+	 * Localizes a script, only if the script has already been added.
+	 *
+	 * @since 2.1.0
+	 * @access public
+	 *
+	 * @param string $handle
+	 * @param string $object_name
+	 * @param array $l10n
+	 * @return bool
+	 */
+	public function localize( $handle, $object_name, $l10n ) {
+		if ( $handle === 'jquery' )
+			$handle = 'jquery-core';
+
+		if ( is_array($l10n) && isset($l10n['l10n_print_after']) ) { // back compat, preserve the code in 'l10n_print_after' if present
+			$after = $l10n['l10n_print_after'];
+			unset($l10n['l10n_print_after']);
+		}
+
+		foreach ( (array) $l10n as $key => $value ) {
+			if ( !is_scalar($value) )
+				continue;
+
+			$l10n[$key] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8');
+		}
+
+		$script = "var $object_name = " . wp_json_encode( $l10n ) . ';';
+
+		if ( !empty($after) )
+			$script .= "\n$after;";
+
+		$data = $this->get_data( $handle, 'data' );
+
+		if ( !empty( $data ) )
+			$script = "$data\n$script";
+
+		return $this->add_data( $handle, 'data', $script );
+	}
+
+	/**
+	 * Sets handle group.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 *
+	 * @see WP_Dependencies::set_group()
+	 *
+	 * @param string    $handle    Name of the item. Should be unique.
+	 * @param bool      $recursion Internal flag that calling function was called recursively.
+	 * @param int|false $group     Optional. Group level: (int) level, (false) no groups. Default false.
+	 * @return bool Not already in the group or a lower group
+	 */
+	public function set_group( $handle, $recursion, $group = false ) {
+		if ( isset( $this->registered[$handle]->args ) && $this->registered[$handle]->args === 1 )
+			$grp = 1;
+		else
+			$grp = (int) $this->get_data( $handle, 'group' );
+
+		if ( false !== $group && $grp > $group )
+			$grp = $group;
+
+		return parent::set_group( $handle, $recursion, $grp );
+	}
+
+	/**
+	 * Determines script dependencies.
+     *
+	 * @since 2.1.0
+	 * @access public
+	 *
+	 * @see WP_Dependencies::all_deps()
+	 *
+	 * @param mixed     $handles   Item handle and argument (string) or item handles and arguments (array of strings).
+	 * @param bool      $recursion Internal flag that function is calling itself.
+	 * @param int|false $group     Optional. Group level: (int) level, (false) no groups. Default false.
+	 * @return bool True on success, false on failure.
+	 */
+	public function all_deps( $handles, $recursion = false, $group = false ) {
+		$r = parent::all_deps( $handles, $recursion, $group );
+		if ( ! $recursion ) {
+			/**
+			 * Filters the list of script dependencies left to print.
+			 *
+			 * @since 2.3.0
+			 *
+			 * @param array $to_do An array of script dependencies.
+			 */
+			$this->to_do = apply_filters( 'print_scripts_array', $this->to_do );
+		}
+		return $r;
+	}
+
+	/**
+	 * Processes items and dependencies for the head group.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 *
+	 * @see WP_Dependencies::do_items()
+	 *
+	 * @return array Handles of items that have been processed.
+	 */
+	public function do_head_items() {
+		$this->do_items(false, 0);
+		return $this->done;
+	}
+
+	/**
+	 * Processes items and dependencies for the footer group.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 *
+	 * @see WP_Dependencies::do_items()
+	 *
+	 * @return array Handles of items that have been processed.
+	 */
+	public function do_footer_items() {
+		$this->do_items(false, 1);
+		return $this->done;
+	}
+
+	/**
+	 * Whether a handle's source is in a default directory.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 *
+	 * @param string $src The source of the enqueued script.
+	 * @return bool True if found, false if not.
+	 */
+	public function in_default_dir( $src ) {
+		if ( ! $this->default_dirs ) {
+			return true;
+		}
+
+		if ( 0 === strpos( $src, '/' . WPINC . '/js/l10n' ) ) {
+			return false;
+		}
+
+		foreach ( (array) $this->default_dirs as $test ) {
+			if ( 0 === strpos( $src, $test ) ) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Resets class properties.
+	 *
+	 * @since 2.8.0
+	 * @access public
+	 */
+	public function reset() {
+		$this->do_concat = false;
+		$this->print_code = '';
+		$this->concat = '';
+		$this->concat_version = '';
+		$this->print_html = '';
+		$this->ext_version = '';
+		$this->ext_handles = '';
+	}
+}
diff --git a/src/wp-includes/class.wp-styles.php b/src/wp-includes/class.wp-styles.php
index fed69962bc..d21f6a03df 100644
--- a/src/wp-includes/class.wp-styles.php
+++ b/src/wp-includes/class.wp-styles.php
@@ -116,6 +116,34 @@ public function __construct() {
 		do_action_ref_array( 'wp_default_styles', array( &$this ) );
 	}
 
+	/**
+	 * Get the HTML element attributes for a stylesheet.
+	 *
+	 * Does not include the `href` attribute.
+	 *
+	 * @since 4.9.2
+	 *
+	 * @param  string $handle The stylesheet handle.
+	 * @return array Attribues.
+	 */
+	public function get_style_additional_attributes( $handle ) {
+		$obj = $this->registered[ $handle ];
+		if ( null === $obj->ver ) {
+			$ver = '';
+		} else {
+			$ver = $obj->ver ? $obj->ver : $this->default_version;
+		}
+
+		$attributes = array(
+			'rel' => isset($obj->extra['alt']) && $obj->extra['alt'] ? 'alternate stylesheet' : 'stylesheet',
+			'id' => $handle . '-css',
+			'title' => isset($obj->extra['title']) ? $obj->extra['title'] : '',
+			'type' => 'text/css',
+			'media' => isset( $obj->args ) ? $obj->args : 'all'
+		);
+		return $attributes;
+	}
+
 	/**
 	 * Processes a style dependency.
 	 *
@@ -192,7 +220,7 @@ public function do_item( $handle ) {
 		 * @param string $href   The stylesheet's source URL.
 		 * @param string $media  The stylesheet's media attribute.
 		 */
-		$tag = apply_filters( 'style_loader_tag', "<link rel='$rel' id='$handle-css' $title href='$href' type='text/css' media='$media' />\n", $handle, $href, $media );
+		$tag = apply_filters( 'style_loader_tag', "<link href='$href'" . $this->get_style_attribute_html( $handle ) . " />\n", $handle, $href, $media );
 		if ( 'rtl' === $this->text_direction && isset( $obj->extra['rtl'] ) && $obj->extra['rtl'] ) {
 			if ( is_bool( $obj->extra['rtl'] ) || 'replace' === $obj->extra['rtl'] ) {
 				$suffix   = isset( $obj->extra['suffix'] ) ? $obj->extra['suffix'] : '';
@@ -234,6 +262,23 @@ public function do_item( $handle ) {
 		return true;
 	}
 
+	/**
+	 * Get the concatenated HTML element attributes for a stylesheet
+	 *
+	 * @since 4.9.2
+	 *
+	 * @param  string $handle The stylesheet handle.
+	 * @return string Concatenated attributes.
+	 */
+	public function get_style_attribute_html( $handle ) {
+		$attributes = $this->get_style_additional_attributes( $handle );
+		$html = '';
+		foreach ( $attributes as $attribute => $attribute_value ) {
+			$html .=  sprintf( " %s='%s'", esc_attr_name( $attribute ), esc_attr( $attribute_value ) );
+		}
+		return $html;
+	}
+
 	/**
 	 * Adds extra CSS styles to a registered stylesheet.
 	 *
diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php
index ce2c851bc5..9bc0c25562 100644
--- a/src/wp-includes/formatting.php
+++ b/src/wp-includes/formatting.php
@@ -4255,6 +4255,34 @@ function esc_attr( $text ) {
 	return apply_filters( 'attribute_escape', $safe_text, $text );
 }
 
+/**
+ * Escaping for HTML attributes.
+ *
+ * @since 2.8.0
+ *
+ * @param string $text
+ * @return string
+ */
+function esc_attr_name( $text ) {
+	$safe_text = wp_check_invalid_utf8( $text );
+	$safe_text = preg_replace( '/[\t\n\f \/>"\'=]+/', '_', $safe_text );
+	/**
+	 * Filters a string cleaned and escaped for output as an HTML attribute name.
+	 *
+	 * Text passed to esc_attr_name() is stripped of invalid or special characters
+	 * before output.
+	 *
+	 * @since joe_bopper patch
+	 *
+	 * @param string $safe_text The text after it has been escaped.
+	 * @param string $text      The text prior to being escaped.
+	 */
+	$safe_text = apply_filters( 'attribute_name_escape', $safe_text, $text );
+
+	//Notably, an attribute name cannot be an empty string.
+	return $safe_text ? $safe_text : 'empty_string_supplied_as_attribute_name';
+}
+
 /**
  * Escaping for textarea values.
  *
diff --git a/src/wp-includes/functions.wp-scripts.php b/src/wp-includes/functions.wp-scripts.php
index d9fab991e7..87dd58a7b6 100644
--- a/src/wp-includes/functions.wp-scripts.php
+++ b/src/wp-includes/functions.wp-scripts.php
@@ -271,10 +271,10 @@ function wp_deregister_script( $handle ) {
  * @see WP_Dependencies::enqueue()
  *
  * @since 2.1.0
+ * @since 4.9.2 Introduced the `$attributes` parameter.
  *
  * @param string           $handle    Name of the script. Should be unique.
  * @param string           $src       Full URL of the script, or path of the script relative to the WordPress root directory.
- *                                    Default empty.
  * @param array            $deps      Optional. An array of registered script handles this script depends on. Default empty array.
  * @param string|bool|null $ver       Optional. String specifying script version number, if it has one, which is added to the URL
  *                                    as a query string for cache busting purposes. If version is set to false, a version
@@ -282,17 +282,25 @@ function wp_deregister_script( $handle ) {
  *                                    If set to null, no version is added.
  * @param bool             $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>.
  *                                    Default 'false'.
+ * @param array            $args {
+ *     Optional script arguments.
+ *
+ *     @type array $attributes Array of script element attributes.
+ *                             Default: array( 'type' => 'text/javascript' )
+ * }
  */
-function wp_enqueue_script( $handle, $src = '', $deps = array(), $ver = false, $in_footer = false ) {
+function wp_enqueue_script( $handle, $src = '', $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 ) {
diff --git a/tests/phpunit/tests/dependencies/getScriptAttributes.php b/tests/phpunit/tests/dependencies/getScriptAttributes.php
new file mode 100644
index 0000000000..24f84b7cef
--- /dev/null
+++ b/tests/phpunit/tests/dependencies/getScriptAttributes.php
@@ -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', array(), '1' );
+
+		$expected_attributes = array(
+			'type' => 'text/javascript',
+			'src' => '/cool-script.js?ver=1',
+		);
+		$actual_attributes = $wp_scripts->get_script_attributes( 'cool-script' );
+		$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?ver=1',
+			'async' => 'async',
+		);
+		$actual_attributes = $wp_scripts->get_script_attributes( 'cool-script' );
+		$this->assertEquals( $expected_attributes, $actual_attributes );
+	}
+
+}
diff --git a/tests/phpunit/tests/dependencies/getScriptAttributesHtml.php b/tests/phpunit/tests/dependencies/getScriptAttributesHtml.php
new file mode 100644
index 0000000000..5a2a766fa9
--- /dev/null
+++ b/tests/phpunit/tests/dependencies/getScriptAttributesHtml.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @group dependencies
+ * @group scripts
+ */
+class Tests_Dependencies_GetScriptAttributesHtml 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_html_with_defaults() {
+		global $wp_scripts;
+		wp_register_script( 'cool-script', '/cool-script.js', array(), '1' );
+
+		$actual = $wp_scripts->get_script_attributes_html( 'cool-script' );
+		$expected = " type='text/javascript' src='/cool-script.js?ver=1'";
+		$this->assertEquals( $expected, $actual );
+	}
+
+	/**
+	 * @ticket 22249
+	 */
+	function test_get_script_attributes_html_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_script_attributes_html( 'cool-script' );
+		$expected = " type='text/javascript' src='/cool-script.js?ver=1' async='async'";
+		$this->assertEquals( $expected, $actual );
+	}
+
+}
diff --git a/tests/phpunit/tests/dependencies/getScriptSrc.php b/tests/phpunit/tests/dependencies/getScriptSrc.php
new file mode 100644
index 0000000000..64d7007963
--- /dev/null
+++ b/tests/phpunit/tests/dependencies/getScriptSrc.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @group dependencies
+ * @group scripts
+ */
+class Tests_Dependencies_GetScriptSrc 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_src_for_script_with_relative_url() {
+		global $wp_scripts;
+		wp_register_script( 'cool-script', '/cool-script.js', array(), '1' );
+
+		$actual = $wp_scripts->get_script_src( 'cool-script' );
+		$this->assertEquals( '/cool-script.js?ver=1', $actual );
+	}
+
+	/**
+	 * @ticket 22249
+	 */
+	function test_get_script_src_for_script_with_full_url() {
+		global $wp_scripts;
+		wp_register_script( 'd3', 'https://d3js.org/d3.v3.min.js', array(), '1' );
+
+		$actual = $wp_scripts->get_script_src( 'd3' );
+		$this->assertEquals( 'https://d3js.org/d3.v3.min.js?ver=1', $actual );
+	}
+
+}
diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php
index 9bbe638b89..d645ba8756 100644
--- a/tests/phpunit/tests/dependencies/scripts.php
+++ b/tests/phpunit/tests/dependencies/scripts.php
@@ -1090,4 +1090,30 @@ public function test_wp_enqueue_code_editor_when_simple_array_will_be_passed() {
 			array_keys( $wp_enqueue_code_editor['htmlhint'] )
 		);
 	}
+
+	/**
+	 * @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 );
+	}
 }
