diff --git a/core/includes/common.inc b/core/includes/common.inc
index 8b241ab..bf642fa 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -890,6 +890,10 @@ function drupal_set_time_limit($time_limit) {
 /**
  * Returns the path to a system item (module, theme, etc.).
  *
+ * 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 $type
  *   The type of the item; one of 'core', 'profile', 'module', 'theme', or
  *   'theme_engine'.
diff --git a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
index 937953f..4419419 100644
--- a/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
+++ b/core/lib/Drupal/Core/StreamWrapper/LocalStream.php
@@ -78,19 +78,25 @@ 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], '\/');
   }
 
   /**
@@ -308,7 +314,7 @@ public function stream_close() {
    * @see http://php.net/manual/streamwrapper.stream-cast.php
    */
   public function stream_cast($cast_as) {
-    return false;
+    return FALSE;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php b/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php
new file mode 100644
index 0000000..efd81a0
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ModuleStream.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ModuleStream.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Defines the read-only module:// stream wrapper for module files.
+ */
+class ModuleStream extends SystemStream {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerName($uri = NULL) {
+    $name = parent::getOwnerName($uri);
+    if (\Drupal::moduleHandler()->moduleExists($name)) {
+      return $name;
+    }
+    else {
+      // The module does not exist or is disabled.
+      throw new \InvalidArgumentException(sprintf('Module %s does not exist or is disabled', $name));
+    }
+  }
+
+  /**
+   * Gets the module's directory path.
+   *
+   * @param string $uri
+   *   Optional URI.
+   *
+   * @return string
+   *   String specifying the path.
+   */
+  public function getDirectoryPath($uri = NULL) {
+    return drupal_get_path('module', $this->getOwnerName($uri));
+  }
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php
new file mode 100644
index 0000000..0e1fd62
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ProfileStream.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ProfileStream.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Defines the read-only profile:// stream wrapper for profiles.
+ */
+class ProfileStream extends SystemStream {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerName($uri = NULL) {
+    $name = parent::getOwnerName($uri);
+    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));
+    }
+  }
+
+  /**
+   * Gets the profile's directory path.
+   *
+   * @param string $uri
+   *   Optional URI.
+   *
+   * @return string
+   *   String specifying the path.
+   */
+  public function getDirectoryPath($uri = NULL) {
+    return drupal_get_path('profile', $this->getOwnerName($uri));
+  }
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
index 3f9f5d4..8fe1d1f 100644
--- a/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
+++ b/core/lib/Drupal/Core/StreamWrapper/StreamWrapperInterface.php
@@ -86,7 +86,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..4460ee6
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/SystemStream.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\SystemStream.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+use \Drupal\Component\Utility\UrlHelper;
+
+/**
+ * Defines a base stream wrapper implementation.
+ *
+ * This class provides a read-only Drupal stream wrapper base class for system
+ * files such as modules, themes and profiles.
+ */
+abstract class SystemStream extends LocalReadOnlyStream {
+
+  /**
+   * Get the module, theme, or profile name of the current URI.
+   *
+   * @param string $uri
+   *   Optional URI.
+   *
+   * @return string
+   *   The extension name.
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function getOwnerName($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(sprintf('Malformed uri parameter passed: %s', $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}
+   */
+  public function getTarget($uri = NULL) {
+    $target = strstr(parent::getTarget($uri), '/') ?: '';
+    if (file_exists($this->getDirectoryPath($uri) . $target)) {
+      return trim($target, '/');
+    }
+    else {
+      throw new \InvalidArgumentException(sprintf('Target %s does not exist', $uri));
+    }
+  }
+
+  /**
+   * Returns a web accessible URL for the resource.
+   *
+   * This function should return a URL that can be embedded in a web page
+   * and accessed from a browser. For example, the external URL of
+   * "youtube://xIpLd0WQKCY" might be
+   * "http://www.youtube.com/watch?v=xIpLd0WQKCY".
+   *
+   * @param string $uri
+   *   An optional URI.
+   *
+   * @return string
+   *   Returns a string containing a web accessible URL for the resource.
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function getExternalUrl($uri = NULL) {
+    $dir = $this->getDirectoryPath($uri);
+    if (empty($dir)) {
+      throw new \InvalidArgumentException(sprintf('Extension directory for %s does not exist.', $uri));
+    }
+
+    // Find the path following the owner name.
+    if ($path = strstr(parent::getTarget($uri), '/')) {
+      // Clean the path.
+      $path = rtrim(UrlHelper::encodePath(str_replace('\\', '/', $path)), '/');
+    }
+
+    return \Drupal::request()->getUri() . $dir . $path;
+  }
+}
diff --git a/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php
new file mode 100644
index 0000000..b7aa3ab
--- /dev/null
+++ b/core/lib/Drupal/Core/StreamWrapper/ThemeStream.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StreamWrapper\ThemeStream.
+ */
+
+namespace Drupal\Core\StreamWrapper;
+
+/**
+ * Defines the read-only theme:// stream wrapper for theme files.
+ */
+class ThemeStream extends SystemStream {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOwnerName($uri = NULL) {
+    $name = parent::getOwnerName($uri);
+    switch ($name) {
+      case 'current':
+        $name = $this->getActiveTheme();
+        break;
+      case 'default':
+        $name =  \Drupal::config('system.theme')->get('default');
+        break;
+      case 'admin':
+        $name =  \Drupal::config('system.theme')->get('admin');
+        break;
+    }
+    // Return name only for enabled themes.
+    if (array_key_exists($name, \Drupal::service('theme_handler')->listInfo())) {
+      return $name;
+    }
+    else {
+      // The theme does not exist or is disabled.
+      throw new \InvalidArgumentException(sprintf('Theme %s does not exist or is disabled', $name));
+    }
+  }
+
+  /**
+   * Gets the theme's directory path.
+   *
+   * @param string $uri
+   *   Optional URI.
+   *
+   * @return string
+   *   String specifying the path.
+   */
+  public function getDirectoryPath($uri = NULL) {
+    return drupal_get_path('theme', $this->getOwnerName($uri));
+  }
+
+  /**
+   * Gets the currently active theme.
+   */
+  protected function getActiveTheme() {
+    return \Drupal::service('theme.negotiator')->determineActiveTheme(\Drupal::service('current_route_match'));
+  }
+}
diff --git a/core/modules/block/tests/modules/block_test/block_test.module b/core/modules/block/tests/modules/block_test/block_test.module
index a527ae1..0583b86 100644
--- a/core/modules/block/tests/modules/block_test/block_test.module
+++ b/core/modules/block/tests/modules/block_test/block_test.module
@@ -8,6 +8,14 @@
 use Drupal\block\BlockPluginInterface;
 
 /**
+ * Implements hook_system_theme_info().
+ */
+function block_test_system_theme_info() {
+  $themes['block_test_theme'] = "module://block_test/themes/block_test_theme/block_test_theme.info.yml";
+  return $themes;
+}
+
+/**
  * Implements hook_block_alter().
  */
 function block_test_block_alter(&$block_info) {
diff --git a/core/modules/breakpoint/tests/breakpoint_theme_test.module b/core/modules/breakpoint/tests/breakpoint_theme_test.module
new file mode 100644
index 0000000..57e8888
--- /dev/null
+++ b/core/modules/breakpoint/tests/breakpoint_theme_test.module
@@ -0,0 +1,13 @@
+<?php
+/**
+ * @file
+ * Test breakpoint functionality for breakpoints provided by themes.
+ */
+
+/**
+ * Implements hook_system_theme_info().
+ */
+function breakpoint_theme_test_system_theme_info() {
+  $themes['breakpoint_test_theme'] = "module://breakpoint_theme_test/themes/breakpoint_test_theme/breakpoint_test_theme.info.yml";
+  return $themes;
+}
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 a850750..0a55965 100644
--- a/core/modules/search/src/Tests/SearchSimplifyTest.php
+++ b/core/modules/search/src/Tests/SearchSimplifyTest.php
@@ -24,7 +24,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/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 616d064..f60ab48 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -61,6 +61,7 @@
 
   private $moduleFiles;
   private $themeFiles;
+  private $themeData;
 
   /**
    * The configuration directories for this test run.
@@ -101,6 +102,7 @@ protected function beforePrepareEnvironment() {
     if (!isset($this->moduleFiles)) {
       $this->moduleFiles = \Drupal::state()->get('system.module.files') ?: array();
       $this->themeFiles = \Drupal::state()->get('system.theme.files') ?: array();
+      $this->themeData = \Drupal::state()->get('system.theme.data') ?: array();
     }
   }
 
@@ -170,6 +172,7 @@ protected function setUp() {
 
     $this->container->get('state')->set('system.module.files', $this->moduleFiles);
     $this->container->get('state')->set('system.theme.files', $this->themeFiles);
+    $this->container->get('state')->set('system.theme.data', $this->themeData);
 
     // Create a minimal core.extension configuration object so that the list of
     // enabled modules can be maintained allowing
diff --git a/core/modules/system/src/Tests/File/SystemStreamUnitTest.php b/core/modules/system/src/Tests/File/SystemStreamUnitTest.php
new file mode 100644
index 0000000..456ec98
--- /dev/null
+++ b/core/modules/system/src/Tests/File/SystemStreamUnitTest.php
@@ -0,0 +1,412 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\File\SystemStreamUnitTest.
+ */
+
+namespace Drupal\system\Tests\File;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Site\Settings;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Unit tests for system stream wrapper functions.
+ *
+ * @group system
+ */
+class SystemStreamUnitTest extends KernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('system', 'file', 'file_test');
+
+  /**
+   * {@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'
+    ));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tearDown() {
+    parent::tearDown();
+  }
+
+  /**
+   * 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 = file_stream_wrapper_get_instance_by_scheme('module');
+
+    foreach ($bad_uris as $bad_uri) {
+      try {
+        $instance->getOwnerName($bad_uri);
+        $this->fail(String::format('Invalid uri %uri not detected.', array('%uri' => $bad_uri)));
+      }
+      catch (\InvalidArgumentException $e) {
+        $this->pass(String::format('Throw exception on invalid uri %uri supplied.', array('%uri' => $bad_uri)));
+      }
+    }
+  }
+
+  /**
+   * Test the Module stream wrapper functions.
+   */
+  public function testModuleStream() {
+    // Generate a module stream wrapper instance.
+    /** @var \Drupal\Core\StreamWrapper\ModuleStream $instance */
+    $instance = file_stream_wrapper_get_instance_by_scheme('module');
+
+    // getOwnerName()
+    $data = $this->providerModuleStream('getOwnerName');
+    $this->runTestOnInstanceMethod($instance, 'getOwnerName', $data);
+
+    // getTarget()
+    $data = $this->providerModuleStream('getTarget');
+    $this->runTestOnInstanceMethod($instance, 'getTarget', $data);
+
+    // getDirectoryPath()
+    $data = $this->providerModuleStream('getDirectoryPath');
+    $this->runTestOnInstanceMethod($instance, 'getDirectoryPath', $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/includes/file_test.dummy.inc',
+      'module://ckeditor/ckeditor.info.yml',
+      'module://foo_bar/foo.bar.js',
+    );
+
+    switch ($method) {
+      case 'getOwnerName':
+        return array_combine($uris, array(
+          array('system', 'Extract module name from a partial URI.'),
+          array('system', 'Extract module name for a resource located in a subdirectory.'),
+          array('file_test', 'Extract module name for a module located in a subdirectory.'),
+          array('file_test', 'Extract module name even for a non-existing resource, as long as the module exists.'),
+          array(new \InvalidArgumentException(), 'Fail returning a disabled module\'s name.'),
+          array(new \InvalidArgumentException(), 'Fail returning a non-existing module\'s name.'),
+        ));
+
+      case 'getTarget':
+        return array_combine($uris, array(
+          array('', 'Return empty target from a partial URI.'),
+          array('css/system.admin.css', 'Extract target for a resource located in a subdirectory.'),
+          array('file_test.dummy.inc', 'Extract target for a module in a non-standard location.'),
+          array(new \InvalidArgumentException(), 'Fail extracting a target for a non-existing resource.'),
+          array(new \InvalidArgumentException(), 'Fail extracting a target within a disabled module.'),
+          array(new \InvalidArgumentException(), 'Fail extracting a target within a non-existing module.'),
+        ));
+
+      case 'getDirectoryPath':
+        return array_combine($uris, array(
+          array('core/modules/system', "Lookup module's directory path for a partial URI."),
+          array('core/modules/system', "Lookup module's directory path for a resource located in a subdirectory."),
+          array('core/modules/file/tests/file_test', "Lookup module's directory path for a module located in a subdirectory."),
+          array('core/modules/file/tests/file_test', "Lookup module's directory path even for a non-existing resource, as long as the module exists."),
+          array(new \InvalidArgumentException(), "Fail lookup of a disabled module's directory path"),
+          array(new \InvalidArgumentException(), "Fail lookup of a non-existing module's directory path."),
+        ));
+
+      case 'getExternalUrl':
+        $base_url = \Drupal::request()->getUri();
+        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/includes/file_test.dummy.inc', "Return the external url even for a non-existing 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-existing module."),
+      ));
+    }
+  }
+
+  /**
+   * Test the Profile stream wrapper functions.
+   */
+  public function testProfileStream() {
+    // Generate a profile stream wrapper instance.
+    /** @var \Drupal\Core\StreamWrapper\ProfileStream $instance */
+    $instance = file_stream_wrapper_get_instance_by_scheme('profile');
+
+    // getOwnerName()
+    $data = $this->providerProfileStream('getOwnerName');
+    $this->runTestOnInstanceMethod($instance, 'getOwnerName', $data);
+
+    // getTarget()
+    $data = $this->providerProfileStream('getTarget');
+    $this->runTestOnInstanceMethod($instance, 'getTarget', $data);
+
+    // getDirectoryPath()
+    $data = $this->providerProfileStream('getDirectoryPath');
+    $this->runTestOnInstanceMethod($instance, 'getDirectoryPath', $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 'getOwnerName':
+        return array_combine($uris, array(
+          array('minimal', "Extract profile's name from a partial URI."),
+          array('minimal', "Extract profile's name for a resource located in a subdirectory."),
+          array('minimal', "Extract profile's name even for a non-existing resource, as long as the profile exists."),
+          array(new \InvalidArgumentException(), "Fail returning a non-existing profile's name."),
+          array('standard', String::format('Lookup real name of %current for a partial URI.', array('%current' => 'profile://current'))),
+          array('standard', String::format('Lookup real name of %current for a resource.', array('%current' => 'profile://current'))),
+        ));
+
+      case 'getTarget':
+        return array_combine($uris, array(
+          array('', 'Return empty target for a partial URI giving only the profile.'),
+          array('config/install/block.block.stark_login.yml', 'Extract target for a resource located in a subdirectory.'),
+          array(new \InvalidArgumentException(), 'Fail returning a target for a non-existing resource.'),
+          array(new \InvalidArgumentException(), 'Fail returning a target within a non-existing profile.'),
+          array('', String::format('Return empty target for a partial URI giving only %current.', array('%current' => 'profile://current'))),
+          array('standard.info.yml', String::format("Extract target from a resource within %current.", array('%current' => 'profile://current'))),
+        ));
+
+      case 'getDirectoryPath':
+        return array_combine($uris, array(
+          array('core/profiles/minimal', "Lookup profile's directory path for a partial URI."),
+          array('core/profiles/minimal', "Lookup profile's directory path for a resource located in a subdirectory."),
+          array('core/profiles/minimal', "Lookup profile's directory path even for a non-existing resource, as long as the profile exists."),
+          array(new \InvalidArgumentException(), "Fail returning a non-existing profile's directory path."),
+          array('core/profiles/standard', String::format('Lookup real directory path of %current for a partial URI.', array('%current' => 'profile://current'))),
+          array('core/profiles/standard', String::format('Lookup real directory path of %current for a resource.', array('%current' => 'profile://current'))),
+        ));
+
+      case 'getExternalUrl':
+        $base_url = \Drupal::request()->getUri();
+        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-existing file located in a subdirectory."),
+          array(new \InvalidArgumentException(), "Fail returning the external uri for a disabled profile."),
+          array($base_url . 'core/profiles/standard', String::format('Lookup the external url of %current for a partial URI.', array('%current' => 'profile://current'))),
+          array($base_url . 'core/profiles/standard/standard.info.yml', String::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');
+
+    // Enable Stark, Bartik and Seven themes.
+    $theme_handler->enable(array('bartik', 'seven', 'stark'));
+
+    // Set admin theme to Seven.
+    $system_theme = \Drupal::config('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', String::format('Make %seven the admin theme.', array('%seven' => 'Seven')));
+
+    // Set default theme to Bartik.
+    $theme_handler->setDefault('bartik');
+    $this->assertEqual($theme_handler->getDefault(), 'bartik', String::format('Make %bartik the default theme.', array('%bartik' => 'Bartik')));
+
+    // Disable Stark theme.
+    $theme_handler->disable(array('stark'));
+
+    /** @var \Drupal\Core\StreamWrapper\ThemeStream $instance */
+    $instance = file_stream_wrapper_get_instance_by_scheme('theme');
+
+    // getOwnerName()
+    $data = $this->providerThemeStream('getOwnerName');
+    $this->runTestOnInstanceMethod($instance, 'getOwnerName', $data);
+
+    // getTarget()
+    $data = $this->providerThemeStream('getTarget');
+    $this->runTestOnInstanceMethod($instance, 'getTarget', $data);
+
+    // getDirectoryPath()
+    $data = $this->providerThemeStream('getDirectoryPath');
+    $this->runTestOnInstanceMethod($instance, 'getDirectoryPath', $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 'getOwnerName':
+        return array_combine($uris, array(
+          array('seven', "Extract theme's name from a partial URI."),
+          array('seven', "Extract theme's name from a full URI."),
+          array('bartik', "Extract theme's name from a full URI with subdirectory."),
+          array(new \InvalidArgumentException(), "Fail returning a non-existing theme's name."),
+          array('bartik', String::format('Lookup real name of %current for a partial URI.', array('%current' => 'theme://current'))),
+          array('bartik', String::format('Lookup real name of %current for a resource.', array('%current' => 'theme://current'))),
+          array('bartik', String::format('Lookup real name of %default for a partial URI.', array('%default' => 'theme://default'))),
+          array('bartik', String::format('Lookup real name of %default for a resource.', array('%default' => 'theme://default'))),
+          array('seven', String::format('Lookup real name of %admin for a partial URI.', array('%admin' => 'theme://admin'))),
+          array('seven', String::format('Lookup real name of %admin even for a non-existing resource.', array('%admin' => 'theme://admin'))),
+          array(new \InvalidArgumentException(), "Fail returning a disabled theme's name."),
+        ));
+
+      case 'getTarget':
+        return array_combine($uris, array(
+          array('', 'Return empty target for a partial URI giving only the theme.'),
+          array(new \InvalidArgumentException(), 'Fail returning a target for a non-existent resource.'),
+          array('color/preview.js', 'Extract target for a resource located in a subdirectory.'),
+          array(new \InvalidArgumentException(), 'Fail returning a target within a non-existing theme.'),
+          array('', String::format('Return empty target for a partial URI giving only %current.', array('%current' => 'theme://current'))),
+          array('logo.png', String::format("Extract target from a resource within %current.", array('%current' => 'theme://current'))),
+          array('', String::format('Return empty target for a partial URI giving only %default.', array('%default' => 'theme://default'))),
+          array('bartik.info.yml', String::format("Extract target from a resource located in a subdirectory of %default.", array('%default' => 'theme://default'))),
+          array('', String::format('Return empty target for a partial URI giving only %admin.', array('%admin' => 'theme://admin'))),
+          array(new \InvalidArgumentException(), String::format("Fail returning target for a non-existing resource within %admin.", array('%admin' => 'theme://admin'))),
+          array(new \InvalidArgumentException(), "Fail returning a target for a disabled theme."),
+        ));
+
+      case 'getDirectoryPath':
+        return array_combine($uris, array(
+          array('core/themes/seven', "Lookup theme's directory path for a partial URI."),
+          array('core/themes/seven', "Lookup theme's directory path for a resource located in a subdirectory."),
+          array('core/themes/bartik', "Lookup theme's directory path for a resource."),
+          array(new \InvalidArgumentException(), "Fail returning a non-existing theme's directory path."),
+          array('core/themes/bartik', String::format('Lookup real directory path of %current for a partial URI.', array('%current' => 'theme://current'))),
+          array('core/themes/bartik', String::format('Lookup real directory path of %current for a resource.', array('%current' => 'theme://current'))),
+          array('core/themes/bartik', String::format('Lookup real directory path of %default for a partial URI.', array('%default' => 'theme://default'))),
+          array('core/themes/bartik', String::format('Lookup real directory path of %default for a resource.', array('%default' => 'theme://default'))),
+          array('core/themes/seven', String::format('Lookup real directory path of %admin for a partial URI.', array('%admin' => 'theme://admin'))),
+          array('core/themes/seven', String::format('Lookup real directory path of %admin for a resource.', array('%admin' => 'theme://admin'))),
+          array(new \InvalidArgumentException(), "Fail returning a disabled theme's directory path."),
+        ));
+
+      case 'getExternalUrl':
+        $base_url = \Drupal::request()->getUri();
+        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-existing theme's external url."),
+          array($base_url . 'core/themes/bartik', String::format('Lookup real external url of %current for a partial URI.', array('%current' => 'theme://current'))),
+          array($base_url . 'core/themes/bartik/logo.png', String::format('Lookup external url of %current for a resource.', array('%current' => 'theme://current'))),
+          array($base_url . 'core/themes/bartik', String::format('Lookup external url of %default for a partial URI.', array('%default' => 'theme://default'))),
+          array($base_url . 'core/themes/bartik/bartik.info.yml', String::format('Lookup external url of %default for a resource.', array('%default' => 'theme://default'))),
+          array($base_url . 'core/themes/seven', String::format('Lookup external url of %admin for a partial URI.', array('%admin' => 'theme://admin'))),
+          array($base_url . 'core/themes/seven/fake.info.yml', String::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) {
+      if ($info[0] instanceof \Exception) {
+        try {
+          $instance->$method($uri);
+          $this->fail($info[1]);
+        }
+        catch (\Exception $e) {
+          if (get_class($e) == get_class($info[0])) {
+            $this->pass($info[1]);
+          }
+        }
+      }
+      else {
+        $this->assertEqual($instance->$method($uri), $info[0]/*, $info[1]*/);
+      }
+    }
+  }
+
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index cad83e5..59b11a7 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -743,6 +743,24 @@ function system_stream_wrappers() {
       'description' => new TranslationWrapper('Temporary local files for upload and previews.'),
       'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
     ),
+    'module' => array(
+      'name' => t('Module files'),
+      'class' => 'Drupal\Core\StreamWrapper\ModuleStream',
+      'description' => t('Local module files.'),
+      'type' => STREAM_WRAPPERS_READ,
+    ),
+    'theme' => array(
+      'name' => t('Theme files'),
+      'class' => 'Drupal\Core\StreamWrapper\ThemeStream',
+      'description' => t('Local theme files.'),
+      'type' => STREAM_WRAPPERS_READ,
+    ),
+    'profile' => array(
+      'name' => t('Profile files'),
+      'class' => 'Drupal\Core\StreamWrapper\ProfileStream',
+      'description' => t('Local profile files.'),
+      'type' => STREAM_WRAPPERS_READ,
+    ),
   );
 
   // Only register the private file stream wrapper if a file path has been set.
diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module
index eee24f3..e427ca7 100644
--- a/core/modules/system/tests/modules/ajax_test/ajax_test.module
+++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module
@@ -13,6 +13,14 @@
 use Drupal\Core\Ajax\HtmlCommand;
 
 /**
+ * Implements hook_system_theme_info().
+ */
+function ajax_test_system_theme_info() {
+  $themes['test_theme'] = "module://system/tests/themes/test_theme/test_theme.info.yml";
+  return $themes;
+}
+
+/**
  * Menu callback: Returns an element suitable for use by
  * \Drupal\Core\Ajax\AjaxResponse::ajaxRender().
  *
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index b6f13c8..b8d3c2a 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -49,13 +49,21 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $items['theme_test_function_template_override'] = array(
     'variables' => array(),
   );
-  $info['test_theme_not_existing_function'] = array(
-    'function' => 'test_theme_not_existing_function',
-  );
   return $items;
 }
 
 /**
+ * Implements hook_system_theme_info().
+ */
+function theme_test_system_theme_info() {
+  $themes['test_theme'] = "module://system/tests/themes/test_theme/test_theme.info.yml";
+  $themes['test_basetheme'] = "module://system/tests/themes/test_basetheme/test_basetheme.info.yml";
+  $themes['test_subtheme'] = "module://system/tests/themes/test_subtheme/test_subtheme.info.yml";
+  $themes['test_theme_phptemplate'] = "module://system/tests/themes/test_theme_phptemplate/test_theme_phptemplate.info.yml";
+  return $themes;
+}
+
+/**
  * Implements hook_preprocess_HOOK() for HTML document templates.
  */
 function theme_test_preprocess_html(&$variables) {
diff --git a/core/modules/update/tests/modules/update_test/update_test.module b/core/modules/update/tests/modules/update_test/update_test.module
index 536d330..b99bf9e 100644
--- a/core/modules/update/tests/modules/update_test/update_test.module
+++ b/core/modules/update/tests/modules/update_test/update_test.module
@@ -10,6 +10,15 @@
  */
 
 /**
+ * Implements hook_system_theme_info().
+ */
+function update_test_system_theme_info() {
+  $themes['update_test_basetheme'] = "module://update/tests/themes/update_test_basetheme/update_test_basetheme.info.yml";
+  $themes['update_test_subtheme'] = "module://update/tests/themes/update_test_subtheme/update_test_subtheme.info.yml";
+  return $themes;
+}
+
+/**
  * Implements hook_system_info_alter().
  *
  * Checks the 'update_test.settings:system_info' configuration and sees if we
