diff --git a/core/core.services.yml b/core/core.services.yml
index d9b1839..e144728 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1270,6 +1270,22 @@ services:
     class: Drupal\Core\StreamWrapper\TemporaryStream
     tags:
       - { name: stream_wrapper, scheme: temporary }
+  stream_wrapper.module:
+    class: Drupal\Core\StreamWrapper\ModuleStream
+    tags:
+      - { name: stream_wrapper, scheme: module }
+  stream_wrapper.theme:
+    class: Drupal\Core\StreamWrapper\ThemeStream
+    tags:
+      - { name: stream_wrapper, scheme: theme }
+  stream_wrapper.profile:
+    class: Drupal\Core\StreamWrapper\ProfileStream
+    tags:
+      - { name: stream_wrapper, scheme: profile }
+  stream_wrapper.library:
+    class: Drupal\Core\StreamWrapper\LibraryStream
+    tags:
+      - { name: stream_wrapper, scheme: library }
   kernel_destruct_subscriber:
     class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber
     tags:
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 589d451..2fd51ad 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -242,14 +242,18 @@ function drupal_get_filename($type, $name, $filename = NULL) {
 /**
  * Returns the path to a system item (module, theme, etc.).
  *
- * @param $type
+ * This function should only be used when including a file containing PHP code;
+ * the 'module://', 'profile://' and 'theme://' stream wrappers should be used
+ * for other use cases.
+ *
+ * @param string $type
  *   The type of the item; one of 'core', 'profile', 'module', 'theme', or
  *   'theme_engine'.
  * @param $name
  *   The name of the item for which the path is requested. Ignored for
  *   $type 'core'.
  *
- * @return
+ * @return string
  *   The path to the requested item or an empty string if the item is not found.
  */
 function drupal_get_path($type, $name) {
diff --git a/core/lib/Drupal/Core/Extension/LibraryDiscovery.php b/core/lib/Drupal/Core/Extension/LibraryDiscovery.php
new file mode 100644
index 0000000..cdfb1ce
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/LibraryDiscovery.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Drupal\Core\Extension;
+
+/**
+ * Discovers available libraries in the filesystem.
+ */
+class LibraryDiscovery extends ExtensionDiscovery {
+
+  /**
+   * The directory libraries are registered in.
+   */
+  const LIBRARIES_DIRECTORY = 'libraries';
+
+  /**
+   * The type of this extension.
+   */
+  const EXTENSION_TYPE = 'library';
+
+  /**
+   * We don't want to affect the static cache of the core ExtensionDiscovery
+   * class, so we keep one separately.
+   *
+   * @var array
+   */
+  protected static $files = array();
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function scanDirectory($dir, $include_tests) {
+    $files = array();
+
+    // In order to scan top-level directories, absolute directory paths have to
+    // be used (which also improves performance, since any configured PHP
+    // include_paths will not be consulted). Retain the relative originating
+    // directory being scanned, so relative paths can be reconstructed below
+    // (all paths are expected to be relative to $this->root).
+    $dir_prefix = ($dir == '' ? '' : "$dir/") . self::LIBRARIES_DIRECTORY . '/';
+    $absolute_dir = ($dir == '' ? $this->root : $this->root . "/$dir");
+    $absolute_dir .= '/' . self::LIBRARIES_DIRECTORY;
+
+    if (!is_dir($absolute_dir)) {
+      return $files;
+    }
+    // Use Unix paths regardless of platform, skip dot directories, follow
+    // symlinks (to allow extensions to be linked from elsewhere), and return
+    // the RecursiveDirectoryIterator instance to have access to getSubPath(),
+    // since SplFileInfo does not support relative paths.
+    $flags = \FilesystemIterator::UNIX_PATHS;
+    $flags |= \FilesystemIterator::SKIP_DOTS;
+    $flags |= \FilesystemIterator::FOLLOW_SYMLINKS;
+    $flags |= \FilesystemIterator::CURRENT_AS_SELF;
+    $directory_iterator = new \RecursiveDirectoryIterator($absolute_dir, $flags);
+
+    /**
+     * @var string $key
+     * @var \RecursiveDirectoryIterator $fileinfo
+     */
+    foreach ($directory_iterator as $key => $fileinfo) {
+      if ($this->fileCache && $cached_extension = $this->fileCache->get($fileinfo->getPathname())) {
+        $files[$cached_extension->getType()][$key] = $cached_extension;
+        continue;
+      }
+
+      if (!$fileinfo->isDir()) {
+        continue;
+      }
+
+      $type = self::EXTENSION_TYPE;
+      $name = $fileinfo->getBasename();
+      $pathname = $dir_prefix . $fileinfo->getSubPathname();
+
+      $extension = new Extension($this->root, $type, $pathname);
+
+      // Track the originating directory for sorting purposes.
+      $extension->subpath = self::LIBRARIES_DIRECTORY . '/' . $fileinfo->getFilename();
+      $extension->origin = $dir;
+
+      $files[$type][$key] = $extension;
+
+      if ($this->fileCache) {
+        $this->fileCache->set($fileinfo->getPathname(), $extension);
+      }
+    }
+
+    return $files;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function scan($type, $include_tests = NULL) {
+    return parent::scan(self::EXTENSION_TYPE);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/ExtensionStreamBase.php b/core/lib/Drupal/Core/StreamWrapper/ExtensionStreamBase.php
new file mode 100644
index 0000000..c0ff68c
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ExtensionStreamBase.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Drupal\Core\StreamWrapper;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Defines a base stream wrapper implementation.
+ *
+ * ExtensionStreamBase is a read-only Drupal stream wrapper base class for
+ * system files located in extensions: modules, themes and installed profile.
+ */
+abstract class ExtensionStreamBase extends LocalReadOnlyStream {
+
+  // @todo Move this in \Drupal\Core\StreamWrapper\LocalStream in Drupal 9.0.x.
+  use StringTranslationTrait;
+
+  /**
+   * The current request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getType() {
+    return StreamWrapperInterface::LOCAL | StreamWrapperInterface::READ;
+  }
+
+  /**
+   * Gets the module, theme, or profile name of the current URI.
+   *
+   * This will return the name of the module, theme or profile e.g.
+   * @code SystemStream::getOwnerName('module://foo') @endcode and @code
+   * SystemStream::getOwnerName('module://foo/')@endcode will both return @code
+   * 'foo'@endcode
+   *
+   * @return string
+   *   The extension name.
+   */
+  protected function getOwnerName() {
+    $uri_parts = explode('://', $this->uri, 2);
+    // Remove the trailing filename from the path.
+    $length = strpos($uri_parts[1], '/');
+    return ($length === FALSE) ? $uri_parts[1] : substr($uri_parts[1], 0, $length);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getTarget($uri = NULL) {
+    if ($target = strstr(parent::getTarget($uri), '/')) {
+      return trim($target, '/');
+    }
+    return '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExternalUrl() {
+    $dir = $this->getDirectoryPath();
+    if (empty($dir)) {
+      throw new \RuntimeException("Extension directory for {$this->uri} does not exist.");
+    }
+    $path = rtrim(base_path() . $dir . '/' . $this->getTarget(), '/');
+    return $this->getRequest()->getUriForPath($path);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dirname($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+    else {
+      $this->uri = $uri;
+    }
+
+    list($scheme) = explode('://', $uri, 2);
+    $dirname = dirname($this->getTarget($uri));
+    $dirname = $dirname !== '.' ? rtrim("/$dirname", '/') : '';
+
+    return "$scheme://{$this->getOwnerName()}{$dirname}";
+  }
+
+  /**
+   * Returns the current request object.
+   *
+   * @return \Symfony\Component\HttpFoundation\Request
+   *   The current request object.
+   */
+  protected function getRequest() {
+    if (!isset($this->request)) {
+      $this->request = \Drupal::service('request_stack')->getCurrentRequest();
+    }
+    return $this->request;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/LibraryStream.php b/core/lib/Drupal/Core/StreamWrapper/LibraryStream.php
new file mode 100644
index 0000000..ee6294d
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/LibraryStream.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Drupal\Core\StreamWrapper;
+
+use Drupal\Core\Extension\LibraryDiscovery;
+
+/**
+ * Defines the read-only library:// stream wrapper for library files.
+ */
+class LibraryStream extends ExtensionStreamBase {
+
+  /**
+   * The path to the Drupal root.
+   *
+   * @var string
+   */
+  protected $drupalRoot;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOwnerName() {
+    $name = parent::getOwnerName();
+    $library_discovery = new LibraryDiscovery($this->getDrupalRoot());
+    $files = $library_discovery->scan($name);
+    if (!isset($files[$name])) {
+      throw new \RuntimeException("Library $name does not exist");
+    }
+
+    return $name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDirectoryPath() {
+    $name = parent::getOwnerName();
+    $library_discovery = new LibraryDiscovery($this->getDrupalRoot());
+    /** @var $files \Drupal\Core\Extension\Extension[] */
+    $files = $library_discovery->scan($name);
+    $name = $this->getOwnerName();
+    return $files[$name]->getPathname();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return $this->t('Library files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->t('Local files stored under the libraries directory.');
+  }
+
+  /**
+   * Return the path to the Drupal root.
+   *
+   * Since stream wrappers don't allow us to pass in __construct() parameters,
+   * we have to fall back to \Drupal.
+   *
+   * @return string
+   */
+  protected function getDrupalRoot() {
+    if (!isset($this->drupalRoot)) {
+      $this->drupalRoot = \Drupal::root();
+    }
+    return $this->drupalRoot;
+  }
+
+  /**
+   * Set the path to the Drupal root.
+   *
+   * @param string $drupalRoot
+   *   The absolute path to the root of the Drupal installation.
+   */
+  public function setDrupalRoot($drupalRoot) {
+    $this->drupalRoot = $drupalRoot;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalReadOnlyStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalReadOnlyStream.php
index 025830c..ac7c30c 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalReadOnlyStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalReadOnlyStream.php
@@ -42,15 +42,7 @@ public function stream_open($uri, $mode, $options, &$opened_path) {
       }
       return FALSE;
     }
-
-    $this->uri = $uri;
-    $path = $this->getLocalPath();
-    $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
-    if ($this->handle !== FALSE && ($options & STREAM_USE_PATH)) {
-      $opened_path = $path;
-    }
-
-    return (bool) $this->handle;
+    return parent::stream_open($uri, $mode, $options, $opened_path);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 7199551..bb0f21e 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -10,32 +10,12 @@
  * "sites/default/files/example.txt" and then PHP filesystem functions are
  * invoked.
  *
- * Drupal\Core\StreamWrapper\LocalStream implementations need to implement at least the
- * getDirectoryPath() and getExternalUrl() methods.
+ * \Drupal\Core\StreamWrapper\LocalStream implementations need to implement at
+ * least the getDirectoryPath() and getExternalUrl() methods.
  */
-abstract class LocalStream implements StreamWrapperInterface {
-  /**
-   * Stream context resource.
-   *
-   * @var resource
-   */
-  public $context;
+abstract class LocalStream extends StreamWrapperBase {
 
-  /**
-   * A generic resource handle.
-   *
-   * @var resource
-   */
-  public $handle = NULL;
-
-  /**
-   * Instance URI (stream).
-   *
-   * A stream is referenced as "scheme://target".
-   *
-   * @var string
-   */
-  protected $uri;
+  use LocalStreamTrait;
 
   /**
    * {@inheritdoc}
@@ -47,58 +27,15 @@ public static function getType() {
   /**
    * Gets the path that the wrapper is responsible for.
    *
-   * @todo Review this method name in D8 per https://www.drupal.org/node/701358.
-   *
    * @return string
    *   String specifying the path.
    */
-  abstract function getDirectoryPath();
-
-  /**
-   * {@inheritdoc}
-   */
-  function setUri($uri) {
-    $this->uri = $uri;
-  }
+  protected abstract function getDirectoryPath();
 
   /**
    * {@inheritdoc}
    */
-  function getUri() {
-    return $this->uri;
-  }
-
-  /**
-   * Returns the local writable target of the resource within the stream.
-   *
-   * This function should be used in place of calls to realpath() or similar
-   * functions when attempting to determine the location of a file. While
-   * functions like realpath() may return the location of a read-only file, this
-   * method may return a URI or path suitable for writing that is completely
-   * separate from the URI used for reading.
-   *
-   * @param string $uri
-   *   Optional URI.
-   *
-   * @return string|bool
-   *   Returns a string representing a location suitable for writing of a file,
-   *   or FALSE if unable to write to the file such as with read-only streams.
-   */
-  protected function getTarget($uri = NULL) {
-    if (!isset($uri)) {
-      $uri = $this->uri;
-    }
-
-    list(, $target) = explode('://', $uri, 2);
-
-    // Remove erroneous leading or trailing, forward-slashes and backslashes.
-    return trim($target, '\/');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  function realpath() {
+  public function realpath() {
     return $this->getLocalPath();
   }
 
@@ -137,6 +74,7 @@ protected function getLocalPath($uri = NULL) {
       $realpath = realpath(dirname($path)) . '/' . drupal_basename($path);
     }
     $directory = realpath($this->getDirectoryPath());
+
     if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
       return FALSE;
     }
@@ -165,7 +103,7 @@ public function stream_open($uri, $mode, $options, &$opened_path) {
     $path = $this->getLocalPath();
     $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
 
-    if ((bool) $this->handle && $options & STREAM_USE_PATH) {
+    if ((bool) $this->handle && ($options & STREAM_USE_PATH)) {
       $opened_path = $path;
     }
 
@@ -395,33 +333,6 @@ public function rename($from_uri, $to_uri) {
   }
 
   /**
-   * Gets the name of the directory from a given path.
-   *
-   * This method is usually accessed through drupal_dirname(), which wraps
-   * around the PHP dirname() function because it does not support stream
-   * wrappers.
-   *
-   * @param string $uri
-   *   A URI or path.
-   *
-   * @return string
-   *   A string containing the directory name.
-   *
-   * @see drupal_dirname()
-   */
-  public function dirname($uri = NULL) {
-    list($scheme) = explode('://', $uri, 2);
-    $target = $this->getTarget($uri);
-    $dirname = dirname($target);
-
-    if ($dirname == '.') {
-      $dirname = '';
-    }
-
-    return $scheme . '://' . $dirname;
-  }
-
-  /**
    * Support for mkdir().
    *
    * @param string $uri
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStreamTrait.php b/core/lib/Drupal/Core/StreamWrapper/LocalStreamTrait.php
new file mode 100644
index 0000000..141f330
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStreamTrait.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Provides common methods for local streams.
+ */
+trait LocalStreamTrait {
+
+  /**
+   * Gets the name of the directory from a given path.
+   *
+   * This method is usually accessed through drupal_dirname(), which wraps
+   * around the PHP dirname() function because it does not support stream
+   * wrappers.
+   *
+   * @param string $uri
+   *   A URI or path.
+   *
+   * @return string
+   *   A string containing the directory name.
+   *
+   * @see drupal_dirname()
+   */
+  public function dirname($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+
+    list($scheme) = explode('://', $uri, 2);
+    $dirname = dirname($this->getTarget($uri));
+
+    return $dirname !== '.' ? "$scheme://$dirname" : "$scheme://";
+  }
+
+  /**
+   * Returns the local writable target of the resource within the stream.
+   *
+   * This function should be used in place of calls to realpath() or similar
+   * functions when attempting to determine the location of a file. While
+   * functions like realpath() may return the location of a read-only file, this
+   * method may return a URI or path suitable for writing that is completely
+   * separate from the URI used for reading.
+   *
+   * @param string $uri
+   *   Optional URI.
+   *
+   * @return string
+   *   Returns a string representing a location suitable for writing of a file.
+   *
+   * @throws \InvalidArgumentException
+   *   If a malformed $uri parameter is passed in.
+   */
+  protected function getTarget($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+
+    $uri_parts = explode('://', $uri, 2);
+    if (count($uri_parts) === 1) {
+      // The delimiter ('://') was not found in $uri, malformed $uri passed.
+      throw new \InvalidArgumentException("Malformed uri parameter passed: $uri");
+    }
+
+    // Remove erroneous leading or trailing forward-slashes and backslashes.
+    return trim($uri_parts[1], '\/');
+  }
+
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php b/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php
new file mode 100644
index 0000000..d88a3c0
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Defines the read-only module:// stream wrapper for module files.
+ */
+class ModuleStream extends ExtensionStreamBase {
+
+  /**
+   * The module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOwnerName() {
+    $name = parent::getOwnerName();
+    if (!$this->getModuleHandler()->moduleExists($name)) {
+      // The module does not exist or is not installed.
+      throw new \RuntimeException("Module $name does not exist or is not installed");
+    }
+    return $name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDirectoryPath() {
+    return $this->getModuleHandler()->getModule($this->getOwnerName())->getPath();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return $this->t('Module files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->t('Local files stored under module directory.');
+  }
+
+  /**
+   * Returns the module handler service.
+   *
+   * @return \Drupal\Core\Extension\ModuleHandlerInterface
+   *   The module handler service.
+   */
+  protected function getModuleHandler() {
+    if (!isset($this->moduleHandler)) {
+      $this->moduleHandler = \Drupal::moduleHandler();
+    }
+    return $this->moduleHandler;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php
new file mode 100644
index 0000000..36f34c3
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Defines the read-only profile:// stream wrapper for installed profile files.
+ */
+class ProfileStream extends ModuleStream {
+
+  use LocalStreamTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOwnerName() {
+    return drupal_get_profile();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return $this->t('Installed profile files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->t('Local files stored under installed profile directory.');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/ReadOnlyStream.php b/core/lib/Drupal/Core/StreamWrapper/ReadOnlyStream.php
index 320f1da..a150deb 100644
--- a/core/lib/Drupal/Core/StreamWrapper/ReadOnlyStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/ReadOnlyStream.php
@@ -12,43 +12,7 @@
  * Drupal\Core\StreamWrapper\ReadOnlyStream implementations need to implement
  * all the read-related classes.
  */
-abstract class ReadOnlyStream implements StreamWrapperInterface {
-  /**
-   * Stream context resource.
-   *
-   * @var resource
-   */
-  public $context;
-
-  /**
-   * A generic resource handle.
-   *
-   * @var resource
-   */
-  public $handle = NULL;
-
-  /**
-   * Instance URI (stream).
-   *
-   * A stream is referenced as "scheme://target".
-   *
-   * @var string
-   */
-  protected $uri;
-
-  /**
-   * {@inheritdoc}
-   */
-  function setUri($uri) {
-    $this->uri = $uri;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  function getUri() {
-    return $this->uri;
-  }
+abstract class ReadOnlyStream extends StreamWrapperBase {
 
   /**
    * Support for fopen(), file_get_contents(), etc.
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperBase.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperBase.php
new file mode 100644
index 0000000..9bea1ad
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperBase.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Provides a base class for all stream wrappers.
+ */
+abstract class StreamWrapperBase implements StreamWrapperInterface {
+
+  /**
+   * Stream context resource.
+   *
+   * @var resource
+   */
+  public $context;
+
+  /**
+   * A generic resource handle.
+   *
+   * @var resource
+   */
+  public $handle = NULL;
+
+  /**
+   * Instance URI (stream).
+   *
+   * A stream is referenced as "scheme://target".
+   *
+   * @var string
+   */
+  protected $uri;
+
+  /**
+   * {@inheritdoc}
+   */
+  function setUri($uri) {
+    if (strpos($uri, '://') === FALSE) {
+      // The delimiter ('://') was not found in $uri, malformed $uri passed.
+      throw new \InvalidArgumentException("Malformed uri parameter passed: {$this->uri}");
+    }
+    $this->uri = $uri;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function getUri() {
+    return $this->uri;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
index 0d38240..5cab825 100644
--- a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
@@ -175,7 +175,7 @@ public function realpath();
    *   An optional URI.
    *
    * @return string
-   *   A string containing the directory name, or FALSE if not applicable.
+   *   A string containing the directory name.
    *
    * @see drupal_dirname()
    */
diff --git a/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php
new file mode 100644
index 0000000..baf65ac
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Defines the read-only theme:// stream wrapper for theme files.
+ */
+class ThemeStream extends ExtensionStreamBase {
+
+  /**
+   * The theme handler service.
+   *
+   * @var \Drupal\Core\Extension\ThemeHandlerInterface
+   */
+  protected $themeHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOwnerName() {
+    $name = parent::getOwnerName();
+    if (!$this->getThemeHandler()->themeExists($name)) {
+      // The theme does not exist or is not installed.
+      throw new \RuntimeException("Theme $name does not exist or is not installed");
+    }
+    return $name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDirectoryPath() {
+    return $this->getThemeHandler()->getTheme($this->getOwnerName())->getPath();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return $this->t('Theme files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return $this->t('Local files stored under theme directory.');
+  }
+
+  /**
+   * Returns the theme handler service.
+   *
+   * @return \Drupal\Core\Extension\ThemeHandlerInterface
+   *   The theme handler service.
+   */
+  protected function getThemeHandler() {
+    if (!isset($this->themeHandler)) {
+      $this->themeHandler = \Drupal::service('theme_handler');
+    }
+    return $this->themeHandler;
+  }
+
+}
diff --git a/core/modules/file/tests/file_test/file_test.dummy.inc b/core/modules/file/tests/file_test/file_test.dummy.inc
new file mode 100644
index 0000000..a09fd2e
--- /dev/null
+++ b/core/modules/file/tests/file_test/file_test.dummy.inc
@@ -0,0 +1 @@
+Dummy file used by SystemStreamTest.php.
diff --git a/core/modules/search/src/Tests/SearchSimplifyTest.php b/core/modules/search/src/Tests/SearchSimplifyTest.php
index 6287526..caefbea 100644
--- a/core/modules/search/src/Tests/SearchSimplifyTest.php
+++ b/core/modules/search/src/Tests/SearchSimplifyTest.php
@@ -20,7 +20,7 @@ function testSearchSimplifyUnicode() {
     // their own lines).  So the even-numbered lines should simplify to nothing,
     // and the odd-numbered lines we need to split into shorter chunks and
     // verify that simplification doesn't lose any characters.
-    $input = file_get_contents(\Drupal::root() . '/core/modules/search/tests/UnicodeTest.txt');
+    $input = file_get_contents('module://search/tests/UnicodeTest.txt');
     $basestrings = explode(chr(10), $input);
     $strings = array();
     foreach ($basestrings as $key => $string) {
diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php
index 4eae7ae..ebd1f61 100644
--- a/core/modules/system/src/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/src/Tests/Theme/ThemeTest.php
@@ -260,7 +260,7 @@ function testClassLoading() {
    */
   public function testFindThemeTemplates() {
     $registry = $this->container->get('theme.registry')->get();
-    $templates = drupal_find_theme_templates($registry, '.html.twig', drupal_get_path('theme', 'test_theme'));
+    $templates = drupal_find_theme_templates($registry, '.html.twig', 'theme://test_theme');
     $this->assertEqual($templates['node__1']['template'], 'node--1', 'Template node--1.tpl.twig was found in test_theme.');
   }
 
diff --git a/core/modules/system/tests/src/Kernel/File/ExtensionStreamTest.php b/core/modules/system/tests/src/Kernel/File/ExtensionStreamTest.php
new file mode 100644
index 0000000..27d5a41
--- /dev/null
+++ b/core/modules/system/tests/src/Kernel/File/ExtensionStreamTest.php
@@ -0,0 +1,333 @@
+<?php
+
+namespace Drupal\Tests\system\Kernel\File;
+
+use Drupal\Core\Site\Settings;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\Filesystem\Filesystem;
+
+/**
+ * Tests system stream wrapper functions.
+ *
+ * @group system
+ */
+class ExtensionStreamTest extends KernelTestBase {
+
+  /**
+   * A list of extension stream wrappers keyed by scheme.
+   *
+   * @var \Drupal\Core\StreamWrapper\StreamWrapperInterface[]
+   */
+  protected $streamWrappers = [];
+
+  /**
+   * The base url for the current request.
+   *
+   * @var string
+   */
+  protected $baseUrl;
+
+  /**
+   * The list of modules to enable.
+   *
+   * @var string[]
+   */
+  public static $modules = ['system'];
+
+  /**
+   * The path to the stub library for testing the library wrapper.
+   *
+   * @var string
+   */
+  protected $stubLibraryPath;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Find the base url to be used later in tests.
+    $this->baseUrl = $this->container->get('request_stack')->getCurrentRequest()->getUriForPath(base_path());
+
+    /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
+    $stream_wrapper_manager = $this->container->get('stream_wrapper_manager');
+    // Get stream wrapper instances.
+    foreach (['module', 'theme', 'profile', 'library'] as $scheme) {
+      $this->streamWrappers[$scheme] = $stream_wrapper_manager->getViaScheme($scheme);
+    }
+
+    /** @var \Drupal\Core\State\StateInterface $state */
+    $state = $this->container->get('state');
+
+    // Set 'minimal' as installed profile for the purposes of this test.
+    $system_module_files = $state->get('system.module.files', []);
+    $system_module_files += ['minimal' => 'core/profiles/minimal/minimal.info.yml'];
+    $state->set('system.module.files', $system_module_files);
+    // Add default profile for the purposes of this test.
+    new Settings(Settings::getAll() +  ['install_profile' => 'minimal']);
+    $this->config('core.extension')->set('module.minimal', 0)->save();
+    $this->container->get('module_handler')->addProfile('minimal', 'core/profiles/minimal');
+
+    /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
+    $theme_installer = $this->container->get('theme_installer');
+    // Install Bartik and Seven themes.
+    $theme_installer->install(['bartik', 'seven']);
+
+    // Create a stub library for testing.
+    $this->stubLibraryPath = \Drupal::service('site.path') . '/libraries/extension-stream-test';
+    $f = new Filesystem();
+    $f->mkdir([$this->stubLibraryPath, $this->stubLibraryPath . '/subdirectory']);
+    $f->touch([$this->stubLibraryPath . '/empty', $this->stubLibraryPath . '/subdirectory/empty']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function tearDown() {
+    parent::tearDown();
+    $f = new Filesystem();
+    $f->remove($this->stubLibraryPath);
+  }
+
+  /**
+   * Tests invalid stream uris.
+   *
+   * @param string $uri
+   *   The URI being tested.
+   *
+   * @dataProvider providerInvalidUris
+   */
+  public function testInvalidStreamUri($uri) {
+    $message = "\\InvalidArgumentException thrown on invalid uri $uri.";
+    try {
+      $this->streamWrappers['module']->dirname($uri);
+      $this->fail($message);
+    }
+    catch (\InvalidArgumentException $e) {
+      $this->assertSame($e->getMessage(), "Malformed uri parameter passed: $uri", $message);
+    }
+  }
+
+  /**
+   * Provides test cases for testInvalidStreamUri()
+   *
+   * @return array[]
+   *   A list of urls to test.
+   */
+  public function providerInvalidUris() {
+    return [
+      ['invalid/uri'],
+      ['invalid_uri'],
+      ['module/invalid/uri'],
+      ['module/invalid_uri'],
+      ['module:invalid_uri'],
+      ['module::/invalid/uri'],
+      ['module::/invalid_uri'],
+      ['module//:invalid/uri'],
+      ['module//invalid_uri'],
+      ['module//invalid/uri'],
+    ];
+  }
+
+  /**
+   * Test the extension stream wrapper methods.
+   *
+   * @param string $uri
+   *   The uri to be tested.
+   * @param string|\RuntimeException|\InvalidArgumentException $dirname
+   *   The expectation for dirname() method.
+   * @param string|\RuntimeException|\InvalidArgumentException $realpath
+   *   The expectation for realpath() method.
+   * @param string|\RuntimeException|\InvalidArgumentException $getExternalUrl
+   *   The expectation for getExternalUrl() method.
+   *
+   * @dataProvider providerStreamWrapperMethods
+   */
+  public function testStreamWrapperMethods($uri, $dirname, $realpath, $getExternalUrl) {
+    // Prefix realpath() expected value with Drupal root directory.
+    $realpath = is_string($realpath) ? DRUPAL_ROOT . $realpath : $realpath;
+    // Prefix getExternalUrl() expected value with base url.
+    $getExternalUrl = is_string($getExternalUrl) ? "{$this->baseUrl}$getExternalUrl" : $getExternalUrl;
+    $case = compact('dirname', 'realpath', 'getExternalUrl');
+
+    foreach ($case as $method => $expected) {
+      list($scheme, ) = explode('://', $uri);
+      $this->streamWrappers[$scheme]->setUri($uri);
+      if ($expected instanceof \InvalidArgumentException) {
+        /** @var \InvalidArgumentException $expected */
+        $message = sprintf('Exception thrown: \InvalidArgumentException("%s").', $expected->getMessage());
+        try {
+          $this->streamWrappers[$scheme]->$method();
+          $this->fail($message);
+        }
+        catch (\InvalidArgumentException $e) {
+          $this->assertSame($expected->getMessage(), $e->getMessage(), $message);
+        }
+      }
+      else if ($expected instanceof \RuntimeException) {
+        /** @var \RuntimeException $expected */
+        $message = sprintf('Exception thrown: \RuntimeException("%s").', $expected->getMessage());
+        try {
+          $this->streamWrappers[$scheme]->$method();
+          $this->fail($message);
+        }
+        catch (\RuntimeException $e) {
+          $this->assertSame($expected->getMessage(), $e->getMessage(), $message);
+        }
+      }
+      elseif (is_string($expected)) {
+        $this->assertSame($expected,  $this->streamWrappers[$scheme]->$method());
+      }
+    }
+  }
+
+  /**
+   * Test when dirname() is called directly without setting a URI first.
+   */
+  public function testDirnameAsParameter() {
+    $this->assertEquals('module://system', $this->streamWrappers['module']->dirname('module://system/system.admin.css'));
+  }
+
+  /**
+   * Provides test cases for testStreamWrapperMethods().
+   *
+   * @return array[]
+   *   A list of test cases. Each case consists of the following items:
+   *   - The uri to be tested.
+   *   - The result or the exception when running dirname() method.
+   *   - The result or the exception when running realpath() method. The value
+   *     is prefixed later, in the test method, with the Drupal root directory.
+   *   - The result or the exception when running getExternalUrl() method. The
+   *     value is prefixed later, in the test method, with the base url.
+   */
+  public function providerStreamWrapperMethods() {
+    return [
+      // Cases for module:// stream wrapper.
+      [
+        'module://system',
+        'module://system',
+        '/core/modules/system',
+        'core/modules/system',
+      ],
+      [
+        'module://system/css/system.admin.css',
+        'module://system/css',
+        '/core/modules/system/css/system.admin.css',
+        'core/modules/system/css/system.admin.css',
+      ],
+      [
+        'module://file_test/file_test.dummy.inc',
+        'module://file_test',
+        '/core/modules/file/tests/file_test/file_test.dummy.inc',
+        'core/modules/file/tests/file_test/file_test.dummy.inc',
+      ],
+      [
+        'module://file_test/src/file_test.dummy.inc',
+        'module://file_test/src',
+        '/core/modules/file/tests/file_test/src/file_test.dummy.inc',
+        'core/modules/file/tests/file_test/src/file_test.dummy.inc',
+      ],
+      [
+        'module://ckeditor/ckeditor.info.yml',
+        new \RuntimeException('Module ckeditor does not exist or is not installed'),
+        new \RuntimeException('Module ckeditor does not exist or is not installed'),
+        new \RuntimeException('Module ckeditor does not exist or is not installed'),
+      ],
+      [
+        'module://foo_bar/foo.bar.js',
+        new \RuntimeException('Module foo_bar does not exist or is not installed'),
+        new \RuntimeException('Module foo_bar does not exist or is not installed'),
+        new \RuntimeException('Module foo_bar does not exist or is not installed'),
+      ],
+      // Cases for theme:// stream wrapper.
+      [
+        'theme://seven',
+        'theme://seven',
+        '/core/themes/seven',
+        'core/themes/seven',
+      ],
+      [
+        'theme://seven/style.css',
+        'theme://seven',
+        '/core/themes/seven/style.css',
+        'core/themes/seven/style.css',
+      ],
+      [
+        'theme://bartik/color/preview.js',
+        'theme://bartik/color',
+        '/core/themes/bartik/color/preview.js',
+        'core/themes/bartik/color/preview.js',
+      ],
+      [
+        'theme://fifteen/screenshot.png',
+        new \RuntimeException('Theme fifteen does not exist or is not installed'),
+        new \RuntimeException('Theme fifteen does not exist or is not installed'),
+        new \RuntimeException('Theme fifteen does not exist or is not installed'),
+      ],
+      [
+        'theme://stark/stark.info.yml',
+        new \RuntimeException('Theme stark does not exist or is not installed'),
+        new \RuntimeException('Theme stark does not exist or is not installed'),
+        new \RuntimeException('Theme stark does not exist or is not installed'),
+      ],
+      // Cases for profile:// stream wrapper.
+      [
+        'profile://',
+        'profile://',
+        '/core/profiles/minimal',
+        'core/profiles/minimal',
+      ],
+      [
+        'profile://config/install/block.block.stark_login.yml',
+        'profile://config/install',
+        '/core/profiles/minimal/config/install/block.block.stark_login.yml',
+        'core/profiles/minimal/config/install/block.block.stark_login.yml',
+      ],
+      [
+        'profile://config/install/node.type.article.yml',
+        'profile://config/install',
+        '/core/profiles/minimal/config/install/node.type.article.yml',
+        'core/profiles/minimal/config/install/node.type.article.yml',
+      ],
+      [
+        'profile://minimal.info.yml',
+        'profile://',
+        '/core/profiles/minimal/minimal.info.yml',
+        'core/profiles/minimal/minimal.info.yml',
+      ],
+      // Cases for library:// stream wrapper.
+      [
+        'library://extension-stream-test',
+        'library://extension-stream-test',
+        '/libraries/extension-stream-test',
+        'libraries/extension-stream-test',
+      ],
+      [
+        'library://extension-stream-test/empty',
+        'library://extension-stream-test',
+        '/libraries/extension-stream-test/empty',
+        'libraries/extension-stream-test/empty',
+      ],
+      [
+        'library://extension-stream-test/subdirectory/empty',
+        'library://extension-stream-test/subdirectory',
+        '/libraries/extension-stream-test/subdirectory/empty',
+        'libraries/extension-stream-test/subdirectory/empty',
+      ],
+      [
+        'library://does-not-exist',
+        new \RuntimeException('Library does-not-exist does not exist'),
+        new \RuntimeException('Library does-not-exist does not exist'),
+        new \RuntimeException('Library does-not-exist does not exist'),
+      ],
+      [
+        'library://does-not-exist/does-not-exist.js',
+        new \RuntimeException('Library does-not-exist does not exist'),
+        new \RuntimeException('Library does-not-exist does not exist'),
+        new \RuntimeException('Library does-not-exist does not exist'),
+      ],
+    ];
+  }
+
+}
