diff --git a/SystemStreamWrapper.inc b/SystemStreamWrapper.inc
deleted file mode 100644
index d9eee80..0000000
--- a/SystemStreamWrapper.inc
+++ /dev/null
@@ -1,182 +0,0 @@
-<?php
-
-/**
- * Drupal system stream wrapper abstract class.
- */
-abstract class SystemStreamWrapper extends DrupalLocalStreamWrapper {
-
-  /**
-   * Get the module, theme, or profile name of the current URI.
-   */
-  protected function getSystemName($uri = NULL) {
-    if (!isset($uri)) {
-      $uri = $this->uri;
-    }
-    list($scheme, $target) = explode('://', $uri, 2);
-    $pos = strpos($target, '/');
-    return $pos === FALSE ? $target : substr($target, 0, $pos);
-  }
-
-  protected function getTarget($uri = NULL) {
-    if (!isset($uri)) {
-      $uri = $this->uri;
-    }
-
-    list($scheme, $target) = explode('://', $uri, 2);
-
-    // Remove erroneous leading or trailing, forward-slashes and backslashes.
-    $target = trim($target, '\/');
-
-    // Remove the module/theme/profile name form the file path. This is always
-    // the first part of the path.
-    $target = explode('/', $target);
-    array_shift($target);
-    $target = implode('/', $target);
-
-    // Trim again.
-    $target = trim($target, '\/');
-    return $target;
-  }
-
-  /**
-   * Gets the path that the wrapper is responsible for.
-   *
-   * @return
-   *   String specifying the path.
-   */
-  //abstract public function getDirectoryPath();
-
-  /**
-   * Overrides getExternalUrl().
-   *
-   * Return the HTML URI of a system file.
-   */
-  public function getExternalUrl() {
-    $dir = $this->getDirectoryPath();
-    if (empty($dir)) {
-      return FALSE;
-    }
-
-    $path = str_replace('\\', '/', $this->getTarget());
-    return $GLOBALS['base_url'] . '/' . $dir . '/' . drupal_encode_path($path);
-  }
-
-  /**
-   * DrupalStreamWrapperInterface requires that these methods be implemented,
-   * but none of them apply to a read-only stream wrapper. On failure they
-   * are expected to return FALSE.
-   */
-
-  public function stream_write($data) {
-    return FALSE;
-  }
-  public function unlink($uri) {
-    // Although the remote file itself can't be deleted, return TRUE so that
-    // file_delete() can remove the file record from the database.
-    return TRUE;
-  }
-  public function rename($from_uri, $to_uri) {
-    return FALSE;
-  }
-  public function mkdir($uri, $mode, $options) {
-    return FALSE;
-  }
-  public function rmdir($uri, $options) {
-    return FALSE;
-  }
-  public function chmod($mode) {
-    return FALSE;
-  }
-  public function dirname($uri = NULL) {
-    return FALSE;
-  }
-}
-
-class ModuleSystemStreamWrapper extends SystemStreamWrapper {
-
-  /**
-   * Implements abstract public function getDirectoryPath()
-   */
-  public function getDirectoryPath() {
-    return drupal_get_path('module', $this->getSystemName());
-  }
-}
-
-/**
- * Stream wrapper for theme files using theme://.
- */
-class ThemeSystemStreamWrapper extends SystemStreamWrapper {
-
-  /**
-   * Override SystemSteamWrapper::getSystemName() to support theme://current,
-   * theme://default, and theme://admin
-   */
-  protected function getSystemName($uri = NULL) {
-    $name = parent::getSystemName($uri);
-    if ($name == 'current') {
-      return $GLOBALS['theme'];
-    }
-    elseif ($name == 'default') {
-      return variable_get('theme_default', 'stark');
-    }
-    elseif ($name == 'admin') {
-      return variable_get('admin_theme', variable_get('theme_default', 'stark'));
-    }
-    else {
-      return $name;
-    }
-  }
-
-  /**
-   * Implements abstract public function getDirectoryPath()
-   */
-  public function getDirectoryPath() {
-    return drupal_get_path('theme', $this->getSystemName());
-  }
-}
-
-/**
- * Stream wrapper for profile files using profile://.
- */
-class ProfileSystemStreamWrapper extends SystemStreamWrapper {
-
-  /**
-   * Override SystemSteamWrapper::getSystemName() to support profile://current
-   */
-  protected function getSystemName($uri = NULL) {
-    $name = parent::getSystemName($uri);
-    if ($name == 'current') {
-      return drupal_get_profile();
-    }
-    else {
-      return $name;
-    }
-  }
-
-  /**
-   * Implements abstract public function getDirectoryPath()
-   */
-  public function getDirectoryPath() {
-    // We cannot use drupal_get_path() here as it actually doesn't work if
-    // $type is 'profile'.
-    // @see http://drupal.org/node/1006714
-    if ($profile = $this->getSystemName()) {
-      return 'profiles/' . $profile;
-    }
-  }
-}
-
-/**
- * Stream wrapper for library files using library://.
- */
-class LibrarySystemStreamWrapper extends SystemStreamWrapper {
-
-  /**
-   * Implements abstract public function getDirectoryPath()
-   */
-  public function getDirectoryPath() {
-    if ($library = $this->getSystemName()) {
-      return libraries_get_path($library);
-    }
-  }
-}
diff --git a/src/StreamWrapper/ExtensionStreamBase.php b/src/StreamWrapper/ExtensionStreamBase.php
new file mode 100644
index 0000000..dadcebd
--- /dev/null
+++ b/src/StreamWrapper/ExtensionStreamBase.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ExtensionStreamBase.
+ */
+
+namespace Drupal\system_stream_wrapper\StreamWrapper;
+
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+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.
+   *
+   * @throws \InvalidArgumentException
+   *   In case of a malformed uri.
+   */
+  protected function getOwnerName() {
+    $uri_parts = explode('://', $this->uri, 2);
+    if (count($uri_parts) === 1) {
+      // The delimiter ('://') was not found in $uri, malformed $uri passed.
+      throw new \InvalidArgumentException("Malformed uri parameter passed: {$this->uri}");
+    }
+
+    // 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 \InvalidArgumentException("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;
+    }
+
+    if (isset($uri)) {
+      $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/src/StreamWrapper/LocalReadOnlyStream.php b/src/StreamWrapper/LocalReadOnlyStream.php
new file mode 100644
index 0000000..3c0f78f
--- /dev/null
+++ b/src/StreamWrapper/LocalReadOnlyStream.php
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\LocalReadOnlyStream.
+ */
+
+namespace Drupal\system_stream_wrapper\StreamWrapper;
+
+/**
+ * Defines a read-only Drupal stream wrapper base class for local files.
+ *
+ * This class extends the complete stream wrapper implementation in LocalStream.
+ * URIs such as "public://example.txt" are expanded to a normal filesystem path
+ * such as "sites/default/files/example.txt" and then PHP filesystem functions
+ * are invoked.
+ *
+ * Drupal\Core\StreamWrapper\LocalReadOnlyStream implementations need to
+ * implement at least the getDirectoryPath() and getExternalUrl() methods.
+ */
+abstract class LocalReadOnlyStream extends LocalStream {
+
+  /**
+   * Support for fopen(), file_get_contents(), etc.
+   *
+   * Any write modes will be rejected, as this is a read-only stream wrapper.
+   *
+   * @param string $uri
+   *   A string containing the URI to the file to open.
+   * @param int $mode
+   *   The file mode, only strict readonly modes are supported.
+   * @param int $options
+   *   A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
+   * @param string $opened_path
+   *   A string containing the path actually opened.
+   *
+   * @return bool
+   *   TRUE if $mode denotes a readonly mode and the file was opened
+   *   successfully, FALSE otherwise.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-open.php
+   */
+  public function stream_open($uri, $mode, $options, &$opened_path) {
+    if (!in_array($mode, array('r', 'rb', 'rt'))) {
+      if ($options & STREAM_REPORT_ERRORS) {
+        trigger_error('stream_open() write modes not supported for read-only stream wrappers', E_USER_WARNING);
+      }
+      return FALSE;
+    }
+    return parent::stream_open($uri, $mode, $options, $opened_path);
+  }
+
+  /**
+   * Support for flock().
+   *
+   * An exclusive lock attempt will be rejected, as this is a read-only stream
+   * wrapper.
+   *
+   * @param int $operation
+   *   One of the following:
+   *   - LOCK_SH to acquire a shared lock (reader).
+   *   - LOCK_EX to acquire an exclusive lock (writer).
+   *   - LOCK_UN to release a lock (shared or exclusive).
+   *   - LOCK_NB added as a bitmask if you don't want flock() to block while
+   *     locking (not supported on Windows).
+   *
+   * @return bool
+   *   Return FALSE for an exclusive lock (writer), as this is a read-only
+   *   stream wrapper.  Return the result of flock() for other valid operations.
+   *   Defaults to TRUE if an invalid operation is passed.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-lock.php
+   */
+  public function stream_lock($operation) {
+    // Disallow exclusive lock or non-blocking lock requests
+    if (in_array($operation, array(LOCK_EX, LOCK_EX|LOCK_NB))) {
+      trigger_error('stream_lock() exclusive lock operations not supported for read-only stream wrappers', E_USER_WARNING);
+      return FALSE;
+    }
+    if (in_array($operation, array(LOCK_SH, LOCK_UN, LOCK_SH|LOCK_NB))) {
+      return flock($this->handle, $operation);
+    }
+
+    return TRUE;
+  }
+
+
+  /**
+   * Support for fwrite(), file_put_contents() etc.
+   *
+   * Data will not be written as this is a read-only stream wrapper.
+   *
+   * @param string $data
+   *   The string to be written.
+   *
+   * @return bool
+   *   FALSE as data will not be written.
+   *
+   * @see http://php.net/manual/en/streamwrapper.stream-write.php
+   */
+  public function stream_write($data) {
+    trigger_error('stream_write() not supported for read-only stream wrappers', E_USER_WARNING);
+    return FALSE;
+  }
+
+  /**
+   * Support for fflush().
+   *
+   * Nothing will be output to the file, as this is a read-only stream wrapper.
+   * However as stream_flush is called during stream_close we should not trigger
+   * an error.
+   *
+   * @return bool
+   *   FALSE, as no data will be stored.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-flush.php
+   */
+  public function stream_flush() {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Does not change meta data as this is a read-only stream wrapper.
+   */
+  public function stream_metadata($uri, $option, $value) {
+    trigger_error('stream_metadata() not supported for read-only stream wrappers', E_USER_WARNING);
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function stream_truncate($new_size) {
+    trigger_error('stream_truncate() not supported for read-only stream wrappers', E_USER_WARNING);
+    return FALSE;
+  }
+
+  /**
+   * Support for unlink().
+   *
+   * The file will not be deleted from the stream as this is a read-only stream
+   * wrapper.
+   *
+   * @param string $uri
+   *   A string containing the uri to the resource to delete.
+   *
+   * @return bool
+   *   TRUE so that file_delete() will remove db reference to file. File is not
+   *   actually deleted.
+   *
+   * @see http://php.net/manual/en/streamwrapper.unlink.php
+   */
+  public function unlink($uri) {
+    trigger_error('unlink() not supported for read-only stream wrappers', E_USER_WARNING);
+    return TRUE;
+  }
+
+  /**
+   * Support for rename().
+   *
+   * The file will not be renamed as this is a read-only stream wrapper.
+   *
+   * @param string $from_uri,
+   *   The uri to the file to rename.
+   * @param string $to_uri
+   *   The new uri for file.
+   *
+   * @return bool
+   *   FALSE as file will never be renamed.
+   *
+   * @see http://php.net/manual/en/streamwrapper.rename.php
+   */
+  public function rename($from_uri, $to_uri) {
+    trigger_error('rename() not supported for read-only stream wrappers', E_USER_WARNING);
+    return FALSE;
+  }
+
+  /**
+   * Support for mkdir().
+   *
+   * Directory will never be created as this is a read-only stream wrapper.
+   *
+   * @param string $uri
+   *   A string containing the URI to the directory to create.
+   * @param int $mode
+   *   Permission flags - see mkdir().
+   * @param int $options
+   *   A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
+   *
+   * @return bool
+   *   FALSE as directory will never be created.
+   *
+   * @see http://php.net/manual/en/streamwrapper.mkdir.php
+   */
+  public function mkdir($uri, $mode, $options) {
+    trigger_error('mkdir() not supported for read-only stream wrappers', E_USER_WARNING);
+    return FALSE;
+  }
+
+  /**
+   * Support for rmdir().
+   *
+   * Directory will never be deleted as this is a read-only stream wrapper.
+   *
+   * @param string $uri
+   *   A string containing the URI to the directory to delete.
+   * @param int $options
+   *   A bit mask of STREAM_REPORT_ERRORS.
+   *
+   * @return bool
+   *   FALSE as directory will never be deleted.
+   *
+   * @see http://php.net/manual/en/streamwrapper.rmdir.php
+   */
+  public function rmdir($uri, $options) {
+    trigger_error('rmdir() not supported for read-only stream wrappers', E_USER_WARNING);
+    return FALSE;
+  }
+
+}
diff --git a/src/StreamWrapper/LocalStream.php b/src/StreamWrapper/LocalStream.php
new file mode 100644
index 0000000..7c08963
--- /dev/null
+++ b/src/StreamWrapper/LocalStream.php
@@ -0,0 +1,524 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\LocalStream.
+ */
+
+namespace Drupal\system_stream_wrapper\StreamWrapper;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+
+/**
+ * Defines a Drupal stream wrapper base class for local files.
+ *
+ * This class provides a complete stream wrapper implementation. URIs such as
+ * "public://example.txt" are expanded to a normal filesystem path such as
+ * "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.
+ */
+abstract class LocalStream implements StreamWrapperInterface {
+
+  use LocalStreamTrait;
+
+  /**
+   * 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}
+   */
+  public static function getType() {
+    return StreamWrapperInterface::NORMAL;
+  }
+
+  /**
+   * Gets the path that the wrapper is responsible for.
+   *
+   * @return string
+   *   String specifying the path.
+   */
+  protected abstract function getDirectoryPath();
+
+  /**
+   * {@inheritdoc}
+   */
+  function setUri($uri) {
+    $this->uri = $uri;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function getUri() {
+    return $this->uri;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function realpath() {
+    return $this->getLocalPath();
+  }
+
+  /**
+   * Returns the canonical absolute path of the URI, if possible.
+   *
+   * @param string $uri
+   *   (optional) The stream wrapper URI to be converted to a canonical
+   *   absolute path. This may point to a directory or another type of file.
+   *
+   * @return string|bool
+   *   If $uri is not set, returns the canonical absolute path of the URI
+   *   previously set by the
+   *   Drupal\Core\StreamWrapper\StreamWrapperInterface::setUri() function.
+   *   If $uri is set and valid for this class, returns its canonical absolute
+   *   path, as determined by the realpath() function. If $uri is set but not
+   *   valid, returns FALSE.
+   */
+  protected function getLocalPath($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+    $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
+
+    // In PHPUnit tests, the base path for local streams may be a virtual
+    // filesystem stream wrapper URI, in which case this local stream acts like
+    // a proxy. realpath() is not supported by vfsStream, because a virtual
+    // file system does not have a real filepath.
+    if (strpos($path, 'vfs://') === 0) {
+      return $path;
+    }
+
+    $realpath = realpath($path);
+    if (!$realpath) {
+      // This file does not yet exist.
+      $realpath = realpath(dirname($path)) . '/' . drupal_basename($path);
+    }
+    $directory = realpath($this->getDirectoryPath());
+
+    if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
+      return FALSE;
+    }
+    return $realpath;
+  }
+
+  /**
+   * Support for fopen(), file_get_contents(), file_put_contents() etc.
+   *
+   * @param string $uri
+   *   A string containing the URI to the file to open.
+   * @param int $mode
+   *   The file mode ("r", "wb" etc.).
+   * @param int $options
+   *   A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
+   * @param string $opened_path
+   *   A string containing the path actually opened.
+   *
+   * @return bool
+   *   Returns TRUE if file was opened successfully.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-open.php
+   */
+  public function stream_open($uri, $mode, $options, &$opened_path) {
+    $this->uri = $uri;
+    $path = $this->getLocalPath();
+    $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
+
+    if ((bool) $this->handle && ($options & STREAM_USE_PATH)) {
+      $opened_path = $path;
+    }
+
+    return (bool) $this->handle;
+  }
+
+  /**
+   * Support for flock().
+   *
+   * @param int $operation
+   *   One of the following:
+   *   - LOCK_SH to acquire a shared lock (reader).
+   *   - LOCK_EX to acquire an exclusive lock (writer).
+   *   - LOCK_UN to release a lock (shared or exclusive).
+   *   - LOCK_NB if you don't want flock() to block while locking (not
+   *     supported on Windows).
+   *
+   * @return bool
+   *   Always returns TRUE at the present time.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-lock.php
+   */
+  public function stream_lock($operation) {
+    if (in_array($operation, array(LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB))) {
+      return flock($this->handle, $operation);
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Support for fread(), file_get_contents() etc.
+   *
+   * @param int $count
+   *   Maximum number of bytes to be read.
+   *
+   * @return string|bool
+   *   The string that was read, or FALSE in case of an error.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-read.php
+   */
+  public function stream_read($count) {
+    return fread($this->handle, $count);
+  }
+
+  /**
+   * Support for fwrite(), file_put_contents() etc.
+   *
+   * @param string $data
+   *   The string to be written.
+   *
+   * @return int
+   *   The number of bytes written.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-write.php
+   */
+  public function stream_write($data) {
+    return fwrite($this->handle, $data);
+  }
+
+  /**
+   * Support for feof().
+   *
+   * @return bool
+   *   TRUE if end-of-file has been reached.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-eof.php
+   */
+  public function stream_eof() {
+    return feof($this->handle);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function stream_seek($offset, $whence = SEEK_SET) {
+    // fseek returns 0 on success and -1 on a failure.
+    // stream_seek   1 on success and  0 on a failure.
+    return !fseek($this->handle, $offset, $whence);
+  }
+
+  /**
+   * Support for fflush().
+   *
+   * @return bool
+   *   TRUE if data was successfully stored (or there was no data to store).
+   *
+   * @see http://php.net/manual/streamwrapper.stream-flush.php
+   */
+  public function stream_flush() {
+    return fflush($this->handle);
+  }
+
+  /**
+   * Support for ftell().
+   *
+   * @return bool
+   *   The current offset in bytes from the beginning of file.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-tell.php
+   */
+  public function stream_tell() {
+    return ftell($this->handle);
+  }
+
+  /**
+   * Support for fstat().
+   *
+   * @return bool
+   *   An array with file status, or FALSE in case of an error - see fstat()
+   *   for a description of this array.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-stat.php
+   */
+  public function stream_stat() {
+    return fstat($this->handle);
+  }
+
+  /**
+   * Support for fclose().
+   *
+   * @return bool
+   *   TRUE if stream was successfully closed.
+   *
+   * @see http://php.net/manual/streamwrapper.stream-close.php
+   */
+  public function stream_close() {
+    return fclose($this->handle);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function stream_cast($cast_as) {
+    return $this->handle ? $this->handle : FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function stream_metadata($uri, $option, $value) {
+    $target = $this->getLocalPath($uri);
+    $return = FALSE;
+    switch ($option) {
+      case STREAM_META_TOUCH:
+        if (!empty($value)) {
+          $return = touch($target, $value[0], $value[1]);
+        }
+        else {
+          $return = touch($target);
+        }
+        break;
+
+      case STREAM_META_OWNER_NAME:
+      case STREAM_META_OWNER:
+        $return = chown($target, $value);
+        break;
+
+      case STREAM_META_GROUP_NAME:
+      case STREAM_META_GROUP:
+        $return = chgrp($target, $value);
+        break;
+
+      case STREAM_META_ACCESS:
+        $return = chmod($target, $value);
+        break;
+    }
+    if ($return) {
+      // For convenience clear the file status cache of the underlying file,
+      // since metadata operations are often followed by file status checks.
+      clearstatcache(TRUE, $target);
+    }
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Since Windows systems do not allow it and it is not needed for most use
+   * cases anyway, this method is not supported on local files and will trigger
+   * an error and return false. If needed, custom subclasses can provide
+   * OS-specific implementations for advanced use cases.
+   */
+  public function stream_set_option($option, $arg1, $arg2) {
+    trigger_error('stream_set_option() not supported for local file based stream wrappers', E_USER_WARNING);
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function stream_truncate($new_size) {
+    return ftruncate($this->handle, $new_size);
+  }
+
+  /**
+   * Support for unlink().
+   *
+   * @param string $uri
+   *   A string containing the URI to the resource to delete.
+   *
+   * @return bool
+   *   TRUE if resource was successfully deleted.
+   *
+   * @see http://php.net/manual/streamwrapper.unlink.php
+   */
+  public function unlink($uri) {
+    $this->uri = $uri;
+    return drupal_unlink($this->getLocalPath());
+  }
+
+  /**
+   * Support for rename().
+   *
+   * @param string $from_uri,
+   *   The URI to the file to rename.
+   * @param string $to_uri
+   *   The new URI for file.
+   *
+   * @return bool
+   *   TRUE if file was successfully renamed.
+   *
+   * @see http://php.net/manual/streamwrapper.rename.php
+   */
+  public function rename($from_uri, $to_uri) {
+    return rename($this->getLocalPath($from_uri), $this->getLocalPath($to_uri));
+  }
+
+  /**
+   * Support for mkdir().
+   *
+   * @param string $uri
+   *   A string containing the URI to the directory to create.
+   * @param int $mode
+   *   Permission flags - see mkdir().
+   * @param int $options
+   *   A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
+   *
+   * @return bool
+   *   TRUE if directory was successfully created.
+   *
+   * @see http://php.net/manual/streamwrapper.mkdir.php
+   */
+  public function mkdir($uri, $mode, $options) {
+    $this->uri = $uri;
+    $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE);
+    if ($recursive) {
+      // $this->getLocalPath() fails if $uri has multiple levels of directories
+      // that do not yet exist.
+      $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
+    }
+    else {
+      $localpath = $this->getLocalPath($uri);
+    }
+    if ($options & STREAM_REPORT_ERRORS) {
+      return drupal_mkdir($localpath, $mode, $recursive);
+    }
+    else {
+      return @drupal_mkdir($localpath, $mode, $recursive);
+    }
+  }
+
+  /**
+   * Support for rmdir().
+   *
+   * @param string $uri
+   *   A string containing the URI to the directory to delete.
+   * @param int $options
+   *   A bit mask of STREAM_REPORT_ERRORS.
+   *
+   * @return bool
+   *   TRUE if directory was successfully removed.
+   *
+   * @see http://php.net/manual/streamwrapper.rmdir.php
+   */
+  public function rmdir($uri, $options) {
+    $this->uri = $uri;
+    if ($options & STREAM_REPORT_ERRORS) {
+      return drupal_rmdir($this->getLocalPath());
+    }
+    else {
+      return @drupal_rmdir($this->getLocalPath());
+    }
+  }
+
+  /**
+   * Support for stat().
+   *
+   * @param string $uri
+   *   A string containing the URI to get information about.
+   * @param int $flags
+   *   A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
+   *
+   * @return array
+   *   An array with file status, or FALSE in case of an error - see fstat()
+   *   for a description of this array.
+   *
+   * @see http://php.net/manual/streamwrapper.url-stat.php
+   */
+  public function url_stat($uri, $flags) {
+    $this->uri = $uri;
+    $path = $this->getLocalPath();
+    // Suppress warnings if requested or if the file or directory does not
+    // exist. This is consistent with PHP's plain filesystem stream wrapper.
+    if ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) {
+      return @stat($path);
+    }
+    else {
+      return stat($path);
+    }
+  }
+
+  /**
+   * Support for opendir().
+   *
+   * @param string $uri
+   *   A string containing the URI to the directory to open.
+   * @param int $options
+   *   Unknown (parameter is not documented in PHP Manual).
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/streamwrapper.dir-opendir.php
+   */
+  public function dir_opendir($uri, $options) {
+    $this->uri = $uri;
+    $this->handle = opendir($this->getLocalPath());
+
+    return (bool) $this->handle;
+  }
+
+  /**
+   * Support for readdir().
+   *
+   * @return string
+   *   The next filename, or FALSE if there are no more files in the directory.
+   *
+   * @see http://php.net/manual/streamwrapper.dir-readdir.php
+   */
+  public function dir_readdir() {
+    return readdir($this->handle);
+  }
+
+  /**
+   * Support for rewinddir().
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/streamwrapper.dir-rewinddir.php
+   */
+  public function dir_rewinddir() {
+    rewinddir($this->handle);
+    // We do not really have a way to signal a failure as rewinddir() does not
+    // have a return value and there is no way to read a directory handler
+    // without advancing to the next file.
+    return TRUE;
+  }
+
+  /**
+   * Support for closedir().
+   *
+   * @return bool
+   *   TRUE on success.
+   *
+   * @see http://php.net/manual/streamwrapper.dir-closedir.php
+   */
+  public function dir_closedir() {
+    closedir($this->handle);
+    // We do not really have a way to signal a failure as closedir() does not
+    // have a return value.
+    return TRUE;
+  }
+}
diff --git a/src/StreamWrapper/LocalStreamTrait.php b/src/StreamWrapper/LocalStreamTrait.php
new file mode 100644
index 0000000..1a39af6
--- /dev/null
+++ b/src/StreamWrapper/LocalStreamTrait.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\LocalStreamTrait.
+ */
+
+namespace Drupal\system_stream_wrapper\StreamWrapper;
+
+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/src/StreamWrapper/ModuleStream.php b/src/StreamWrapper/ModuleStream.php
new file mode 100644
index 0000000..0b6592d
--- /dev/null
+++ b/src/StreamWrapper/ModuleStream.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ModuleStream.
+ */
+
+namespace Drupal\system_stream_wrapper\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 \InvalidArgumentException("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/src/StreamWrapper/ProfileStream.php b/src/StreamWrapper/ProfileStream.php
new file mode 100644
index 0000000..0852f28
--- /dev/null
+++ b/src/StreamWrapper/ProfileStream.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ProfileStream.
+ */
+
+namespace Drupal\system_stream_wrapper\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/src/StreamWrapper/ThemeStream.php b/src/StreamWrapper/ThemeStream.php
new file mode 100644
index 0000000..eac18e9
--- /dev/null
+++ b/src/StreamWrapper/ThemeStream.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ThemeStream.
+ */
+
+namespace Drupal\system_stream_wrapper\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 \InvalidArgumentException("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/system_stream_wrapper.info b/system_stream_wrapper.info
deleted file mode 100644
index 92348b3..0000000
--- a/system_stream_wrapper.info
+++ /dev/null
@@ -1,4 +0,0 @@
-name = System stream wrapper
-description = Provides stream wrappers to access files in module, theme, profile, and library files and directories.
-core = 7.x
-files[] = SystemStreamWrapper.inc
diff --git a/system_stream_wrapper.info.yml b/system_stream_wrapper.info.yml
new file mode 100644
index 0000000..204285e
--- /dev/null
+++ b/system_stream_wrapper.info.yml
@@ -0,0 +1,4 @@
+name: System stream wrapper
+type: module
+description: Provides stream wrappers to access files in module, theme, profile, and library files and directories.
+core: 8.x
diff --git a/system_stream_wrapper.module b/system_stream_wrapper.module
deleted file mode 100644
index 0f336a4..0000000
--- a/system_stream_wrapper.module
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-/**
- * Implements hook_stream_wrappers().
- */
-function system_stream_wrapper_stream_wrappers() {
-  $wrappers['module'] = array(
-    'name' => t('Module files'),
-    'class' => 'ModuleSystemStreamWrapper',
-    'description' => t('Local module files.'),
-    'type' => STREAM_WRAPPERS_READ,
-  );
-  $wrappers['theme'] = array(
-    'name' => t('Theme files'),
-    'class' => 'ThemeSystemStreamWrapper',
-    'description' => t('Local theme files.'),
-    'type' => STREAM_WRAPPERS_READ,
-  );
-  $wrappers['profile'] = array(
-    'name' => t('Profile files'),
-    'class' => 'ProfileSystemStreamWrapper',
-    'description' => t('Local profile files.'),
-    'type' => STREAM_WRAPPERS_READ,
-  );
-
-  // Add optional support for libraries module.
-  if (module_exists('libraries')) {
-    $wrappers['library'] = array(
-      'name' => t('Library files'),
-      'class' => 'LibrarySystemStreamWrapper',
-      'description' => t('Local library files.'),
-      'type' => STREAM_WRAPPERS_READ,
-    );
-  }
-
-  return $wrappers;
-}
diff --git a/system_stream_wrapper.services.yml b/system_stream_wrapper.services.yml
new file mode 100644
index 0000000..e1c8136
--- /dev/null
+++ b/system_stream_wrapper.services.yml
@@ -0,0 +1,13 @@
+services:
+  stream_wrapper.module:
+    class: Drupal\system_stream_wrapper\StreamWrapper\ModuleStream
+    tags:
+      - { name: stream_wrapper, scheme: module }
+  stream_wrapper.theme:
+    class: Drupal\system_stream_wrapper\StreamWrapper\ThemeStream
+    tags:
+      - { name: stream_wrapper, scheme: theme }
+  stream_wrapper.profile:
+    class: Drupal\system_stream_wrapper\StreamWrapper\ProfileStream
+    tags:
+      - { name: stream_wrapper, scheme: profile }
diff --git a/tests/src/Kernel/File/ExtensionStreamTest.php b/tests/src/Kernel/File/ExtensionStreamTest.php
new file mode 100644
index 0000000..d109423
--- /dev/null
+++ b/tests/src/Kernel/File/ExtensionStreamTest.php
@@ -0,0 +1,264 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\system_stream_wrapper\Kernel\File\ExtensionStreamTest.
+ */
+
+namespace Drupal\Tests\system_stream_wrapper\Kernel\File;
+
+use Drupal\Core\Site\Settings;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests system stream wrapper functions.
+ *
+ * @group system_stream_wrapper
+ */
+class ExtensionStreamTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['system', 'system_stream_wrapper'];
+
+  /**
+   * 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;
+
+  /**
+   * {@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'] 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']);
+  }
+
+  /**
+   * 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|\InvalidArgumentException $dirname
+   *   The expectation for dirname() method.
+   * @param string|\InvalidArgumentException $realpath
+   *   The expectation for realpath() method.
+   * @param string|\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);
+        }
+      }
+      elseif (is_string($expected)) {
+        $this->assertSame($expected,  $this->streamWrappers[$scheme]->$method());
+      }
+    }
+  }
+
+  /**
+   * 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 \InvalidArgumentException('Module ckeditor does not exist or is not installed'),
+        new \InvalidArgumentException('Module ckeditor does not exist or is not installed'),
+        new \InvalidArgumentException('Module ckeditor does not exist or is not installed'),
+      ],
+      [
+        'module://foo_bar/foo.bar.js',
+        new \InvalidArgumentException('Module foo_bar does not exist or is not installed'),
+        new \InvalidArgumentException('Module foo_bar does not exist or is not installed'),
+        new \InvalidArgumentException('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 \InvalidArgumentException('Theme fifteen does not exist or is not installed'),
+        new \InvalidArgumentException('Theme fifteen does not exist or is not installed'),
+        new \InvalidArgumentException('Theme fifteen does not exist or is not installed'),
+      ],
+      [
+        'theme://stark/stark.info.yml',
+        new \InvalidArgumentException('Theme stark does not exist or is not installed'),
+        new \InvalidArgumentException('Theme stark does not exist or is not installed'),
+        new \InvalidArgumentException('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',
+      ],
+    ];
+  }
+
+}
