diff --git a/core/core.services.yml b/core/core.services.yml
index 1aadb75..364a48f 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1209,6 +1209,21 @@ services:
     class: Drupal\Core\StreamWrapper\TemporaryStream
     tags:
       - { name: stream_wrapper, scheme: temporary }
+  stream_wrapper.module:
+    class: Drupal\Core\StreamWrapper\ModuleStream
+    arguments: ['@request_stack', '@module_handler']
+    tags:
+      - { name: stream_wrapper, scheme: module }
+  stream_wrapper.theme:
+    class: Drupal\Core\StreamWrapper\ThemeStream
+    arguments: ['@request_stack', '@theme_handler', '@theme.negotiator', '@config.factory', '@current_route_match']
+    tags:
+      - { name: stream_wrapper, scheme: theme }
+  stream_wrapper.profile:
+    class: Drupal\Core\StreamWrapper\ProfileStream
+    arguments: ['@request_stack']
+    tags:
+      - { name: stream_wrapper, scheme: profile }
   kernel_destruct_subscriber:
     class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber
     tags:
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 20b124b..d87b737 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -265,14 +265,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/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 021c541..490abcd 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -57,19 +57,19 @@ public static function getType() {
    * @return string
    *   String specifying the path.
    */
-  abstract function getDirectoryPath();
+  protected abstract function getDirectoryPath();
 
   /**
    * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::setUri().
    */
-  function setUri($uri) {
+  public function setUri($uri) {
     $this->uri = $uri;
   }
 
   /**
    * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getUri().
    */
-  function getUri() {
+  public function getUri() {
     return $this->uri;
   }
 
@@ -85,25 +85,31 @@ function getUri() {
    * @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.
+   * @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;
     }
 
-    list(, $target) = explode('://', $uri, 2);
+    $uri_parts = explode('://', $uri, 2);
+    if (count($uri_parts) === 1) {
+      // The delimiter ('://') was not found in $uri, malformed $uri passed.
+      throw new \InvalidArgumentException(sprintf('Malformed uri parameter passed: %s', $uri));
+    }
 
-    // Remove erroneous leading or trailing, forward-slashes and backslashes.
-    return trim($target, '\/');
+    // Remove erroneous leading or trailing forward-slashes and backslashes.
+    return trim($uri_parts[1], '\/');
   }
 
   /**
    * Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::realpath().
    */
-  function realpath() {
+  public function realpath() {
     return $this->getLocalPath();
   }
 
@@ -126,7 +132,7 @@ protected function getLocalPath($uri = NULL) {
     if (!isset($uri)) {
       $uri = $this->uri;
     }
-    $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
+    $path = $this->getDirectoryPath() . DIRECTORY_SEPARATOR . $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
@@ -139,7 +145,7 @@ protected function getLocalPath($uri = NULL) {
     $realpath = realpath($path);
     if (!$realpath) {
       // This file does not yet exist.
-      $realpath = realpath(dirname($path)) . '/' . drupal_basename($path);
+      $realpath = realpath(dirname($path)) . DIRECTORY_SEPARATOR . drupal_basename($path);
     }
     $directory = realpath($this->getDirectoryPath());
     if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
@@ -447,7 +453,7 @@ public function mkdir($uri, $mode, $options) {
     if ($recursive) {
       // $this->getLocalPath() fails if $uri has multiple levels of directories
       // that do not yet exist.
-      $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri);
+      $localpath = $this->getDirectoryPath() . DIRECTORY_SEPARATOR . $this->getTarget($uri);
     }
     else {
       $localpath = $this->getLocalPath($uri);
@@ -572,4 +578,5 @@ public function dir_closedir() {
     // have a return value.
     return TRUE;
   }
+
 }
diff --git a/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php b/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php
new file mode 100644
index 0000000..0a878a3
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ModuleStream.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Defines the read-only module:// stream wrapper for module files.
+ */
+class ModuleStream extends SystemStream {
+
+  /**
+   * Wraps the ModuleHandler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a ModuleStream wrapper object.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack from which the current request is retrieved.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(RequestStack $request_stack, ModuleHandlerInterface $module_handler) {
+    parent::__construct($request_stack);
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOwnerName() {
+    $name = parent::getOwnerName();
+    if ($this->moduleHandler->moduleExists($name)) {
+      return $name;
+    }
+    else {
+      // The module does not exist or is not installed.
+      throw new \InvalidArgumentException(sprintf('Module %s does not exist or is not installed', $name));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDirectoryPath() {
+    return $this->moduleHandler->getModule($this->getOwnerName())->getPath();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Module files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Local module files.');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php
new file mode 100644
index 0000000..de03e6d
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ProfileStream.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Defines the read-only profile:// stream wrapper for profile files.
+ */
+class ProfileStream extends SystemStream {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOwnerName() {
+    $name = parent::getOwnerName();
+    if ($name == 'current') {
+      $name = drupal_get_profile();
+    }
+
+    if (!is_null(drupal_get_filename('profile', $name))) {
+      return $name;
+    }
+    else {
+      // The profile does not exist.
+      throw new \InvalidArgumentException(sprintf('Profile %s does not exist', $name));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDirectoryPath() {
+    return dirname(drupal_get_filename('profile', $this->getOwnerName()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Profile files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Local profile files.');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
index 34dc333..ea2297a 100644
--- a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
@@ -67,6 +67,7 @@
 
   /**
    * Not visible in the UI or accessible via web, but readable and writable.
+   *
    * E.g. the temporary directory for uploads.
    */
   const HIDDEN = 0x000C;
@@ -180,7 +181,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/SystemStream.php b/core/lib/Drupal/Core/StreamWrapper/SystemStream.php
new file mode 100644
index 0000000..a4d96a8
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/SystemStream.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\SystemStream.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Defines a base stream wrapper implementation.
+ *
+ * SystemStream is a read-only Drupal stream wrapper base class for system files
+ * located in modules, themes and profiles.
+ */
+abstract class SystemStream extends LocalReadOnlyStream {
+  /**
+   * The request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * Constructs a new system stream.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack from which the current request is retrieved.
+   */
+  public function __construct(RequestStack $request_stack) {
+    $this->request = $request_stack->getCurrentRequest();
+  }
+
+  /**
+   * {@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
+   */
+  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(sprintf('Malformed uri parameter passed: %s', $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) {
+    return rtrim(strstr(parent::getTarget($uri), '/') ?: '', '/');
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function getExternalUrl() {
+    $dir = $this->getDirectoryPath();
+    if (empty($dir)) {
+      throw new \InvalidArgumentException(sprintf('Extension directory for %s does not exist.', $this->uri));
+    }
+    return $this->request->getUriForPath(base_path() . $dir . $this->getTarget());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dirname($uri = NULL) {
+    if (!isset($uri)) {
+      $uri = $this->uri;
+    }
+    list($scheme) = explode('://', $uri, 2);
+    $target = $this->getTarget($uri);
+    $dirname = dirname($target);
+
+    if ($dirname === '.' || $dirname === '\\') {
+      $dirname = '';
+    }
+
+    return rtrim($scheme . '://' . $this->getOwnerName() . $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..a02b01a
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ThemeStream.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Extension\ThemeHandlerInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Theme\ThemeNegotiatorInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Defines the read-only theme:// stream wrapper for theme files.
+ */
+class ThemeStream extends SystemStream {
+
+  /**
+   * The theme handler service.
+   *
+   * @var \Drupal\Core\Extension\ThemeHandlerInterface
+   */
+  protected $themeHandler;
+
+  /**
+   * The theme negotiator service.
+   *
+   * @var \Drupal\Core\Theme\ThemeNegotiatorInterface
+   */
+  protected $themeNegotiator;
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * The route matching service.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * Constructs a new ThemeStream object.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack from which the current request is retrieved.
+   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
+   *   The theme handler.
+   * @param \Drupal\Core\Theme\ThemeNegotiatorInterface $theme_negotiator
+   *   The theme negotiator.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config
+   *   The config manager.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route matcher.
+   */
+  public function __construct(RequestStack $request_stack, ThemeHandlerInterface $theme_handler, ThemeNegotiatorInterface $theme_negotiator, ConfigFactoryInterface $config, RouteMatchInterface $route_match) {
+    parent::__construct($request_stack);
+    $this->themeHandler = $theme_handler;
+    $this->themeNegotiator = $theme_negotiator;
+    $this->configFactory = $config;
+    $this->routeMatch = $route_match;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getOwnerName() {
+    $name = parent::getOwnerName();
+    switch ($name) {
+      case 'current':
+        $name = $this->getActiveTheme();
+        break;
+
+      case 'default':
+        $name = $this->themeHandler->getDefault();
+        break;
+
+      case 'admin':
+        $name = $this->configFactory->get('system.theme')->get('admin');
+        break;
+
+    }
+    // Return name only for installed themes.
+    if ($this->themeHandler->themeExists($name)) {
+      return $name;
+    }
+    else {
+      // The theme does not exist or is not installed.
+      throw new \InvalidArgumentException(sprintf('Theme %s does not exist or is not installed', $name));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDirectoryPath() {
+    return $this->themeHandler->getTheme($this->getOwnerName())->getPath();
+  }
+
+  /**
+   * Gets the currently active theme.
+   *
+   * @return string|null
+   *   Returns the active theme name, else return NULL.
+   */
+  protected function getActiveTheme() {
+    return $this->themeNegotiator->determineActiveTheme($this->routeMatch);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return t('Theme files');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return t('Local theme files.');
+  }
+
+}
diff --git a/core/modules/color/tests/modules/color_test/color_test.module b/core/modules/color/tests/modules/color_test/color_test.module
index 603906e..b89e5e4 100644
--- a/core/modules/color/tests/modules/color_test/color_test.module
+++ b/core/modules/color/tests/modules/color_test/color_test.module
@@ -9,6 +9,6 @@
  * Implements hook_system_theme_info().
  */
 function color_test_system_theme_info() {
-  $themes['color_test_theme'] = drupal_get_path('module', 'color_test') . '/themes/color_test_theme/color_test_theme.info.yml';
+  $themes['color_test_theme'] = 'module://color_test/themes/color_test_theme/color_test_theme.info.yml';
   return $themes;
 }
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 82363fd..6780ba0 100644
--- a/core/modules/search/src/Tests/SearchSimplifyTest.php
+++ b/core/modules/search/src/Tests/SearchSimplifyTest.php
@@ -25,7 +25,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/File/SystemStreamTest.php b/core/modules/system/src/Tests/File/SystemStreamTest.php
new file mode 100644
index 0000000..1f7d42f
--- /dev/null
+++ b/core/modules/system/src/Tests/File/SystemStreamTest.php
@@ -0,0 +1,376 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\File\SystemStreamTest.
+ */
+
+namespace Drupal\system\Tests\File;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Site\Settings;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Unit tests for system stream wrapper functions.
+ *
+ * @group system
+ */
+class SystemStreamTest extends KernelTestBase {
+
+  /**
+   * Modules to install for this test.
+   *
+   * @var array
+   */
+  public static $modules = array('system', 'file', 'file_test');
+
+  /**
+   * The profile to install as a basis for testing.
+   *
+   * @var string
+   */
+  protected $profile = 'standard';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'System Stream wrappers',
+      'description' => 'Unit tests system stream wrapper functions.',
+      'group' => 'File API',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    drupal_static_reset('file_get_stream_wrappers');
+
+    // Add default profile for the purposes of this test.
+//    new Settings(Settings::getAll() + array(
+//      'install_profile' => 'standard'
+//    ));
+  }
+
+  /**
+   * Test Invalid stream uri.
+   */
+  public function testInvalidStreamUriException() {
+    $bad_uris = array(
+      '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',
+    );
+
+    /** @var \Drupal\Core\StreamWrapper\ModuleStream $instance */
+    $instance = \Drupal::service('stream_wrapper_manager')->getViaScheme('module');
+
+    foreach ($bad_uris as $bad_uri) {
+      try {
+        $instance->dirname($bad_uri);
+        $this->fail(SafeMarkup::format('Invalid uri %uri not detected.', array('%uri' => $bad_uri)));
+      }
+      catch (\InvalidArgumentException $e) {
+        if ($e->getMessage() == 'Malformed uri parameter passed: ' . $bad_uri) {
+          $this->pass(SafeMarkup::format('Throw exception on invalid uri %uri supplied.', array('%uri' => $bad_uri)));
+        }
+        else {
+          throw new \InvalidArgumentException($e);
+        }
+      }
+    }
+  }
+
+  /**
+   * Test the Module stream wrapper functions.
+   */
+  public function testModuleStream() {
+    // Generate a module stream wrapper instance.
+    /** @var \Drupal\Core\StreamWrapper\ModuleStream $instance */
+    $instance = \Drupal::service('stream_wrapper_manager')->getViaScheme('module');
+
+    // dirname()
+    $data = $this->providerModuleStream('dirname');
+    $this->runTestOnInstanceMethod($instance, 'dirname', $data);
+
+    // realpath()
+    $data = $this->providerModuleStream('realpath');
+    $this->runTestOnInstanceMethod($instance, 'realpath', $data);
+
+    // getExternalUrl()
+    $data = $this->providerModuleStream('getExternalUrl');
+    $this->runTestOnInstanceMethod($instance, 'getExternalUrl', $data);
+  }
+
+  /**
+   * Provides test data for testModuleStream().
+   */
+  protected function providerModuleStream($method) {
+    $uris = array(
+      'module://system',
+      'module://system/css/system.admin.css',
+      'module://file_test/file_test.dummy.inc',
+      'module://file_test/src/file_test.dummy.inc',
+      'module://ckeditor/ckeditor.info.yml',
+      'module://foo_bar/foo.bar.js',
+    );
+    switch ($method) {
+      case 'dirname':
+        return array_combine($uris, array(
+          array('module://system', 'Get the dirname of an installed module.'),
+          array('module://system/css', 'Get the dirname of a resource located in a subdirectory of an installed module.'),
+          array('module://file_test', 'Get the dirname of a resource in a module located in a subdirectory.'),
+          array('module://file_test/src', 'Get the dirname of a non-existent resource in a subdirectory of a module.'),
+          array(new \InvalidArgumentException(), 'Fail returning an uninstalled module\'s dirname.'),
+          array(new \InvalidArgumentException(), 'Fail returning a non-existent module\'s dirname.'),
+        ));
+
+      case 'realpath':
+        $base_path = DRUPAL_ROOT;
+        return array_combine($uris, array(
+          array($base_path . '/core/modules/system', 'Get the realpath of an installed module.'),
+          array($base_path . '/core/modules/system/css/system.admin.css', 'Get the realpath of a resource located in a subdirectory of an installed module.'),
+          array($base_path . '/core/modules/file/tests/file_test/file_test.dummy.inc', 'Get the realpath of a resource in a module located in a subdirectory.'),
+          array($base_path . '/core/modules/file/tests/file_test/src/file_test.dummy.inc', 'Get the realpath of a non-existent resource in a subdirectory of a module.'),
+          array(new \InvalidArgumentException(), 'Fail returning an uninstalled module\'s basepath.'),
+          array(new \InvalidArgumentException(), 'Fail returning a non-existent module\'s basepath.'),
+        ));
+
+      case 'getExternalUrl':
+        $base_url = \Drupal::request()->getUriForPath(base_path());
+        return array_combine($uris, array(
+          array($base_url . 'core/modules/system', "Return the external url for the module directory path."),
+          array($base_url . 'core/modules/system/css/system.admin.css', "Return the external url of a resource located in a subdirectory."),
+          array($base_url . 'core/modules/file/tests/file_test/file_test.dummy.inc', "Return the external url of an include file located in a subdirectory."),
+          array($base_url . 'core/modules/file/tests/file_test/src/file_test.dummy.inc', "Return the external url even for a non-existent resource, as long as the module exists."),
+          array(new \InvalidArgumentException(), "Fail returning the external uri for resources in a disabled module."),
+          array(new \InvalidArgumentException(), "Fail returning the external uri for resources in a non-existent module."),
+      ));
+    }
+  }
+
+  /**
+   * Test the Profile stream wrapper functions.
+   */
+  public function testProfileStream() {
+    // Generate a profile stream wrapper instance.
+    /** @var \Drupal\Core\StreamWrapper\ProfileStream $instance */
+    $instance = \Drupal::service('stream_wrapper_manager')->getViaScheme('profile');
+
+    // dirname()
+    $data = $this->providerProfileStream('dirname');
+    $this->runTestOnInstanceMethod($instance, 'dirname', $data);
+
+    // realpath()
+    $data = $this->providerProfileStream('realpath');
+    $this->runTestOnInstanceMethod($instance, 'realpath', $data);
+
+    // getExternalUrl()
+    $data = $this->providerProfileStream('getExternalUrl');
+    $this->runTestOnInstanceMethod($instance, 'getExternalUrl', $data);
+  }
+
+  /**
+   * Provides test data for testProfileStream().
+   */
+  protected function providerProfileStream($method) {
+    $uris = array(
+      'profile://minimal',
+      'profile://minimal/config/install/block.block.stark_login.yml',
+      'profile://minimal/config/install/node.type.article.yml',
+      'profile://foo_bar/',
+      'profile://current',
+      'profile://current/standard.info.yml',
+    );
+
+    switch ($method) {
+      case 'dirname':
+        return array_combine($uris, array(
+          array('profile://minimal', "Get the dirname of an installed profile."),
+          array('profile://minimal/config/install', "Get the dirname of a resource located in a profile's subdirectory."),
+          array('profile://minimal/config/install', "Get the dirname of a non-existent resource in profile's subdirectory."),
+          array(new \InvalidArgumentException(), "Fail returning the dirname of a non-existent profile."),
+          array('profile://standard', SafeMarkup::format('Get the dirname of %current.', array('%current' => 'profile://current'))),
+          array('profile://standard', SafeMarkup::format('Get the dirname of a resource in %current path.', array('%current' => 'profile://current'))),
+        ));
+
+      case 'realpath':
+        $base_path = DRUPAL_ROOT;
+        return array_combine($uris, array(
+          array($base_path . '/core/profiles/minimal', "Get the realpath of an installed profile."),
+          array($base_path . '/core/profiles/minimal/config/install/block.block.stark_login.yml', "Get the realpath of a resource located in a profile's subdirectory."),
+          array($base_path . '/core/profiles/minimal/config/install/node.type.article.yml', "Get the realpath of a non-existent resource in profile's subdirectory."),
+          array(new \InvalidArgumentException(), "Fail returning the realpath for a non-existent profile."),
+          array($base_path . '/core/profiles/standard', SafeMarkup::format('Get the realpath of %current.', array('%current' => 'profile://current'))),
+          array($base_path . '/core/profiles/standard/standard.info.yml', SafeMarkup::format('Get the realpath of a resource in %current path.', array('%current' => 'profile://current'))),
+        ));
+
+      case 'getExternalUrl':
+        $base_url = \Drupal::request()->getUriForPath(base_path());
+        return array_combine($uris, array(
+          array($base_url . 'core/profiles/minimal', "Return the external url for the profile directory path."),
+          array($base_url . 'core/profiles/minimal/config/install/block.block.stark_login.yml', "Return the external url of a file located in a subdirectory."),
+          array($base_url . 'core/profiles/minimal/config/install/node.type.article.yml', "Return the external url of a non-existent file located in a subdirectory."),
+          array(new \InvalidArgumentException(), "Fail returning the external uri for a disabled profile."),
+          array($base_url . 'core/profiles/standard', SafeMarkup::format('Lookup the external url of %current for a partial URI.', array('%current' => 'profile://current'))),
+          array($base_url . 'core/profiles/standard/standard.info.yml', SafeMarkup::format('Lookup the external url of %current for a resource.', array('%current' => 'profile://current'))),
+        ));
+    }
+  }
+
+  /**
+   * Test the Theme stream wrapper functions.
+   */
+  public function testThemeStream() {
+    // Generate a theme stream wrapper instance.
+    /** @var \Drupal\Core\Extension\ThemeHandler $theme_handler */
+    $theme_handler = \Drupal::service('theme_handler');
+
+    // Install Stark, Bartik and Seven themes.
+    $theme_handler->install(array('bartik', 'seven', 'stark'));
+
+    // Set admin theme to Seven.
+    $system_theme = \Drupal::configFactory()->getEditable('system.theme');
+    $this->assertNull($system_theme->get('admin'), 'No admin theme was set.');
+    $system_theme->set('admin', 'seven')->save();
+    $this->assertEqual($system_theme->get('admin'), 'seven', SafeMarkup::format('Make %seven the admin theme.', array('%seven' => 'Seven')));
+
+    // Set default theme to Bartik.
+    $theme_handler->setDefault('bartik');
+    $this->assertEqual($theme_handler->getDefault(), 'bartik', SafeMarkup::format('Make %bartik the default theme.', array('%bartik' => 'Bartik')));
+
+    // Uninstall Stark theme.
+    $theme_handler->uninstall(array('stark'));
+
+    /** @var \Drupal\Core\StreamWrapper\ThemeStream $instance */
+    $instance = \Drupal::service('stream_wrapper_manager')->getViaScheme('theme');
+
+    // dirname()
+    $data = $this->providerThemeStream('dirname');
+    $this->runTestOnInstanceMethod($instance, 'dirname', $data);
+
+    // realpath()
+    $data = $this->providerThemeStream('realpath');
+    $this->runTestOnInstanceMethod($instance, 'realpath', $data);
+
+    // getExternalUrl()
+    $data = $this->providerThemeStream('getExternalUrl');
+    $this->runTestOnInstanceMethod($instance, 'getExternalUrl', $data);
+  }
+
+  /**
+   * Provides test data for testThemeStream()
+   */
+  protected function providerThemeStream($method) {
+    $uris = array(
+      'theme://seven',
+      'theme://seven/style.css',
+      'theme://bartik/color/preview.js',
+      'theme://fifteen/screenshot.png',
+      'theme://current',
+      'theme://current/logo.png',
+      'theme://default',
+      'theme://default/bartik.info.yml',
+      'theme://admin',
+      'theme://admin/fake.info.yml',
+      'theme://stark/stark.info.yml',
+    );
+
+    switch ($method) {
+      case 'dirname':
+        return array_combine($uris, array(
+          array('theme://seven', "Get the dirname of an installed theme."),
+          array('theme://seven', "Get the dirname of a resource located in a theme directory."),
+          array('theme://bartik/color', "Get the dirname of a resource located in a theme's subdirectory."),
+          array(new \InvalidArgumentException(), "Fail returning the dirname of a non-existent theme."),
+          array('theme://bartik', SafeMarkup::format('Get the dirname of %current.', array('%current' => 'theme://current'))),
+          array('theme://bartik', SafeMarkup::format('Get the dirname of a resource in %current.', array('%current' => 'theme://current'))),
+          array('theme://bartik', SafeMarkup::format('Get the dirname of %default.', array('%default' => 'theme://default'))),
+          array('theme://bartik', SafeMarkup::format('Get the dirname of a resource in %default.', array('%default' => 'theme://default'))),
+          array('theme://seven', SafeMarkup::format('Get the dirname of %admin.', array('%admin' => 'theme://admin'))),
+          array('theme://seven', SafeMarkup::format('Get the dirname of a non-existent resource in %admin.', array('%admin' => 'theme://admin'))),
+          array(new \InvalidArgumentException(), "Fail returning the dirname for an uninstalled theme."),
+        ));
+
+      case 'realpath':
+        $base_path = DRUPAL_ROOT;
+        return array_combine($uris, array(
+          array($base_path . '/core/themes/seven', "Get the realpath of an installed theme."),
+          array($base_path . '/core/themes/seven/style.css', "Get the realpath of a resource located in a theme directory."),
+          array($base_path . '/core/themes/bartik/color/preview.js', "Get the realpath of a resource located in a theme's subdirectory."),
+          array(new \InvalidArgumentException(), "Fail returning the realpath of a non-existent theme."),
+          array($base_path . '/core/themes/bartik', SafeMarkup::format('Get the realpath of %current.', array('%current' => 'theme://current'))),
+          array($base_path . '/core/themes/bartik/logo.png', SafeMarkup::format('Get the realpath of a resource in %current.', array('%current' => 'theme://current'))),
+          array($base_path . '/core/themes/bartik', SafeMarkup::format('Get the realpath of %default.', array('%default' => 'theme://default'))),
+          array($base_path . '/core/themes/bartik/bartik.info.yml', SafeMarkup::format('Get the realpath of a resource in %default.', array('%default' => 'theme://default'))),
+          array($base_path . '/core/themes/seven', SafeMarkup::format('Get the realpath of %admin.', array('%admin' => 'theme://admin'))),
+          array($base_path . '/core/themes/seven/fake.info.yml', SafeMarkup::format('Get the realpath of a non-existent resource in %admin.', array('%admin' => 'theme://admin'))),
+          array(new \InvalidArgumentException(), "Fail returning the realpath for an uninstalled theme."),
+        ));
+
+      case 'getExternalUrl':
+        $base_url = \Drupal::request()->getUriForPath(base_path());
+        return array_combine($uris, array(
+          array($base_url . 'core/themes/seven', "Lookup theme's external url for a partial URI."),
+          array($base_url . 'core/themes/seven/style.css', "Lookup theme's external url for a resource located in a subdirectory."),
+          array($base_url . 'core/themes/bartik/color/preview.js', "Lookup theme's external url for a resource."),
+          array(new \InvalidArgumentException(), "Fail returning a non-existent theme's external url."),
+          array($base_url . 'core/themes/bartik', SafeMarkup::format('Lookup real external url of %current for a partial URI.', array('%current' => 'theme://current'))),
+          array($base_url . 'core/themes/bartik/logo.png', SafeMarkup::format('Lookup external url of %current for a resource.', array('%current' => 'theme://current'))),
+          array($base_url . 'core/themes/bartik', SafeMarkup::format('Lookup external url of %default for a partial URI.', array('%default' => 'theme://default'))),
+          array($base_url . 'core/themes/bartik/bartik.info.yml', SafeMarkup::format('Lookup external url of %default for a resource.', array('%default' => 'theme://default'))),
+          array($base_url . 'core/themes/seven', SafeMarkup::format('Lookup external url of %admin for a partial URI.', array('%admin' => 'theme://admin'))),
+          array($base_url . 'core/themes/seven/fake.info.yml', SafeMarkup::format('Lookup external url of %admin for a resource.', array('%admin' => 'theme://admin'))),
+          array(new \InvalidArgumentException(), "Fail returning a disabled theme's external url."),
+        ));
+    }
+  }
+
+
+  /**
+   * Helper method to run specific tests on each StreamWrapper method.
+   *
+   * @param \Drupal\Core\StreamWrapper\SystemStream $instance
+   *   The stream wrapper instance to carry out the test on.
+   * @param string $method
+   *   The SystemStream method to be tested.
+   * @param array $data
+   *   An array of data to be used for the test, containing the following info:
+   *   - The expected result.
+   *   - The test result message.
+   */
+  private function runTestOnInstanceMethod($instance, $method, array $data) {
+    foreach ($data as $uri => $info) {
+      $instance->setUri($uri);
+      if ($info[0] instanceof \InvalidArgumentException) {
+        try {
+          $instance->$method();
+          $this->fail($info[1]);
+        }
+        catch (\InvalidArgumentException $e) {
+          $this->pass($info[1]);
+        }
+      }
+      else {
+        if ($method == 'realpath') {
+          // realpath() returns strings with the OS-specific DIRECTORY_SEPARATOR,
+          // make sure we are testing for that.
+          $info[0] = str_replace('/', DIRECTORY_SEPARATOR, $info[0]);
+        }
+        $this->assertEqual($instance->$method(), $info[0], $info[1]);
+      }
+    }
+  }
+
+}
