diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index fb18fe3..fbdea4c 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -603,7 +603,7 @@ function install_run_task($task, &$install_state) {
       // Otherwise, the batch will complete.
       // Disable the default script for the URL and clone the object, as
       // batch_process() will add additional options to the batch URL.
-      $url = Url::fromUri('base:install.php', ['query' => $install_state['parameters'], 'script' => '']);
+      $url = Url::fromBaseRelativePath('install.php', ['query' => $install_state['parameters'], 'script' => '']);
       $response = batch_process($url, clone $url);
       if ($response instanceof Response) {
         if ($session = \Drupal::request()->getSession()) {
diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index 5da8ffb8..95ef46c 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -18,11 +18,98 @@
 
 /**
  * Defines an object that holds information about a URL.
+ *
+ * This class supports:
+ * - External URLs.
+ * - Internal URLs that correspond to routes handled by Drupal's routing system.
+ * - Base-relative and host-relative URLs not handled by Drupal.
+ * - URLs for user input.
+ * - URLs for entities.
+ *
+ * New URLs should generally be constructed using one of the static factory
+ * methods. See:
+ * - Url::fromRoute()
+ * - Url::fromRouteMatch()
+ * - Url::fromUri()
+ * - Url::fromUserInput()
+ * - Url::fromBaseRelativePath()
+ * - Url::fromHostRelativePath()
  */
 class Url {
+
   use DependencySerializationTrait;
 
   /**
+   * Scheme for URIs to resolve as references relative to Drupal's installation.
+   *
+   * Use this scheme for linking to pages on the same host as the Drupal site,
+   * but not generated by Drupal.
+   *
+   * https://tools.ietf.org/html/rfc3986#section-5.2 specifies the standard for
+   * resolving URI references relative to a base URI. This scheme allows such
+   * references to be internally represented and managed in Drupal as full URIs
+   * themselves, and resolved to the corresponding URLs at runtime, using
+   * Drupal's installation URL as the base URI.
+   *
+   * For example, if Drupal is installed in http://example.com/drupal/, then:
+   * - Url::fromUri(Url::SCHEME_BASE . ':README.txt')->toString() and
+   *   Url::fromBaseRelativePath('README.txt')->toString() would both return
+   *   '/drupal/README.txt'.
+   * - Url::fromUri(Url::SCHEME_BASE . ':/index.html')->toString() and
+   *   Url::fromHostRelativePath('index.html')->toString() would both return
+   *   '/index.html'.
+   *
+   * This is the identical resolution as what a browser would perform if Drupal
+   * output a @link http://www.w3.org/wiki/HTML/Elements/base <base href="/drupal/"> @endlink
+   * element and the above URLs were written within links as:
+   * - @code <a href="README.txt"></a> @endcode (browser resolves as
+   *   '/drupal/README.txt')
+   * - @code <a href="/index.html"></a> @endcode (browser resolves as
+   *   '/index.html')
+   *
+   * Do not use this scheme for linking to pages generated by Drupal. With this
+   * scheme, path processors are not applied and access checking is bypassed.
+   * This means:
+   * - Links in multilingual sites will be incorrect because path prefixing
+   *   is not applied.
+   * - Search engine optimization will degrade because path aliases will be
+   *   ignored.
+   * - Users may see links they do not have acccess to.
+   * Use Url::fromRoute() to generate URLs for pages generated by Drupal.
+   *
+   * @see \Drupal\Core\Url::fromBaseRelativePath()
+   * @see \Drupal\Core\Url::fromHostRelativePath()
+   * @see \Drupal\Core\Url::fromRoute()
+   * @see \Drupal\Core\Url::fromUri()
+   */
+  const SCHEME_BASE = 'base';
+
+  /**
+   * Scheme for entity URIs.
+   *
+   * This scheme should be used for entity URIs, which are given in the form
+   * entity:{entity_type}/{entity_id}. To get the URL for an entity, generally
+   * you should use \Drupal\Core\Entity\Entity::url() rather than creating a
+   * URI directly.
+   *
+   * @see \Drupal\Core\Url::fromEntityUri()
+   */
+  const SCHEME_ENTITY = 'entity';
+
+  /**
+   * Scheme for routed URIs.
+   *
+   * This scheme is used internally for storing routes as URIs. To get the
+   * URL for a route, use Url::fromRoute().
+   *
+   * @see \Drupal\Core\Url::fromRouteUri()
+   *
+   * @todo What exactly is the point of this scheme? See:
+   *   https://www.drupal.org/node/2418139
+   */
+  const SCHEME_ROUTE = 'route';
+
+  /**
    * The URL generator.
    *
    * @var \Drupal\Core\Routing\UrlGeneratorInterface
@@ -44,7 +131,7 @@ class Url {
   protected $accessManager;
 
   /**
-   * The route name.
+   * The route name for the URL, if any.
    *
    * @var string
    */
@@ -97,12 +184,18 @@ class Url {
   /**
    * Constructs a new Url object.
    *
-   * In most cases, use Url::fromRoute() or Url::fromUri() rather than
-   * constructing Url objects directly in order to avoid ambiguity and make your
-   * code more self-documenting.
+   * In most cases, use one of the static factory methods rather than
+   * constructing Url objects directly, in order to avoid ambiguity and make
+   * your code more self-documenting:
+   * - Url::fromRoute()
+   * - Url::fromRouteMatch()
+   * - Url::fromUri()
+   * - Url::fromUserInput()
+   * - Url::fromBaseRelativePath()
+   * - Url::fromHostRelativePath()
    *
    * @param string $route_name
-   *   The name of the route
+   *   The name of the route.
    * @param array $route_parameters
    *   (optional) An associative array of parameter names and values.
    * @param array $options
@@ -121,12 +214,6 @@ class Url {
    *   - 'https': Whether this URL should point to a secure location. If not
    *     defined, the current scheme is used, so the user stays on HTTP or HTTPS
    *     respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
-   *
-   * @see static::fromRoute()
-   * @see static::fromUri()
-   *
-   * @todo Update this documentation for non-routed URIs in
-   *   https://www.drupal.org/node/2346787
    */
   public function __construct($route_name, $route_parameters = array(), $options = array()) {
     $this->routeName = $route_name;
@@ -137,9 +224,9 @@ public function __construct($route_name, $route_parameters = array(), $options =
   /**
    * Creates a new Url object for a URL that has a Drupal route.
    *
-   * This method is for URLs that have Drupal routes (that is, most pages
-   * generated by Drupal). For non-routed local URIs relative to the base
-   * path (like robots.txt) use Url::fromUri() with the base: scheme.
+   * This method is for URLs that have Drupal routes (pages generated by
+   * Drupal). To generate a URL for a route match instead (that is, the route
+   * corresponding to a particular request), use Url::fromRouteMatch().
    *
    * @param string $route_name
    *   The name of the route
@@ -162,10 +249,12 @@ public function __construct($route_name, $route_parameters = array(), $options =
    *     defined, the current scheme is used, so the user stays on HTTP or HTTPS
    *     respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
    *
-   * @return \Drupal\Core\Url
+   * @return static
    *   A new Url object for a routed (internal to Drupal) URL.
    *
+   * @see \Drupal\Core\Url::fromRouteMatch()
    * @see \Drupal\Core\Url::fromUserInput()
+   * @see \Drupal\Core\Url::fromBaseRelativePath()
    * @see \Drupal\Core\Url::fromUri()
    */
   public static function fromRoute($route_name, $route_parameters = array(), $options = array()) {
@@ -173,12 +262,20 @@ public static function fromRoute($route_name, $route_parameters = array(), $opti
   }
 
   /**
-   * Creates a new URL object from a route match.
+   * Creates a new Url object from a route match (e.g. for the current request).
+   *
+   * Use this method to create a URL from a specific route match, that is, the
+   * route that best corresponds to a particular request. This is often used to
+   * generate URLs based on the URL for the current request. To generate a URL
+   * for a specific route (rather than from a request), use Url::fromRoute().
    *
    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
    *   The route match.
    *
-   * @return $this
+   * @return static
+   *
+   * @see \Drupal\Core\Url::fromRoute()
+   * @see \Drupal\Core\Routing\RouteMatchInterface
    */
   public static function fromRouteMatch(RouteMatchInterface $route_match) {
     if ($route_match->getRouteObject()) {
@@ -190,6 +287,73 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
   }
 
   /**
+   * Creates a new Url object from an unrouted, base-relative path.
+   *
+   * This method should only be used for paths within the base directory of the
+   * site installation that do not have a Drupal route. These may be files like
+   * 'robots.txt', core scripts like 'core/install.php', or they may be from
+   *  another application placed within the Drupal site installation.
+   *
+   * Do not use this method for URLs that have a Drupal route (pages generated
+   * by Drupal) or for host-relative paths. See the documentation of
+   * Url::SCHEME_BASE for details.
+   *
+   * @param string $path
+   *   The base-relative path, for example 'robots.txt'. Leading slashes are
+   *   discarded.
+   * @param array $options
+   *   (optional) An array of options. See Url::fromUri() for details.
+   *
+   * @return static
+   *   A new Url object for an unrouted URL within the site installation
+   *   directory.
+   *
+   * @see \Drupal\Core\Url::SCHEME_BASE
+   * @see \Drupal\Core\Url::fromHostRelativePath()
+   * @see \Drupal\Core\Url::fromUri()
+   * @see \Drupal\Core\Url::fromRoute()
+   */
+  public static function fromBaseRelativePath($path, $options = array()) {
+    // Trim leading slashes to ensure a base-relative path is used.
+    $path = ltrim($path, '/');
+    return static::fromUri(static::SCHEME_BASE . ':' . $path, $options);
+  }
+
+  /**
+   * Creates a new Url object from an unrouted, host-relative path.
+   *
+   * This method should only be used for paths within the same host as the
+   * site installation that do not have a Drupal route and are not within the
+   * Drupal installation directory.
+   *
+   * Do not use this method for URLs that have a Drupal route (pages generated
+   * by Drupal) or for base-relative paths. See the documentation of
+   * Url::SCHEME_BASE for details.
+   *
+   * @param string $path
+   *   The host-relative path. For example, if Drupal is installed in the
+   *   subdirectory http://example.com/drupal/, use 'index.html' for a URL to
+   *   http://example.com/index.html. Leading slashes are discarded.
+   * @param array $options
+   *   (optional) An array of options. See Url::fromUri() for details.
+   *
+   * @return static
+   *   A new Url object for an unrouted, host-relative URL not internal to
+   *   Drupal, but relative to the site host.
+   *
+   * @see \Drupal\Core\Url::SCHEME_BASE
+   * @see \Drupal\Core\Url::fromBaseRelativePath()
+   * @see \Drupal\Core\Url::fromUri()
+   * @see \Drupal\Core\Url::fromRoute()
+   */
+  public static function fromHostRelativePath($path, $options = array()) {
+    // Trim leading slashes, then add exactly one, to ensure a host-relative
+    // path is used.
+    $path = ltrim($path, '/');
+    return static::fromUri(static::SCHEME_BASE . ':/' . $path, $options);
+  }
+
+  /**
    * Creates a Url object for a relative URI reference submitted by user input.
    *
    * Use this method to create a URL for user-entered paths that may or may not
@@ -242,24 +406,28 @@ public static function fromUserInput($user_input, $options = []) {
   /**
    * Creates a new Url object from a URI.
    *
-   * This method is for generating URLs for URIs that:
-   * - do not have Drupal routes: both external URLs and unrouted local URIs
-   *   like base:robots.txt
-   * - do have a Drupal route but have a custom scheme to simplify linking.
-   *   Currently, there is only the entity: scheme (This allows URIs of the
-   *   form entity:{entity_type}/{entity_id}. For example: entity:node/1
-   *   resolves to the entity.node.canonical route with a node parameter of 1.)
+   * This method is used to create URLs for internal and external URIs with a
+   * unified API and storage format.
+   *
+   * - For URIs that have Drupal routes (pages generated by Drupal), use
+   *   Url::fromRoute() whenever possible, to avoid circumventing the routing
+   *   system.
+   * - For paths from user input that may or may not correspond to a valid
+   *   Drupal route, use Url::fromUserInput().
+   * - For base-relative and host-relative paths that are not created by Drupal,
+   *   use Url::fromBaseRelativePath() and Url::fromHostRelativePath(),
+   *   respectively.
+   * - For entity URIs, use \Drupal\core\Entity\EntityInterface::url().
    *
-   * For URLs that have Drupal routes (that is, most pages generated by Drupal),
-   * use Url::fromRoute().
+   * You can call Url::access() on the returned object to do access checking.
    *
    * @param string $uri
-   *   The URI of the resource including the scheme. For user input that may
-   *   correspond to a Drupal route, use internal: for the scheme. For paths
-   *   that are known not to be handled by the Drupal routing system (such as
-   *   static files), use base: for the scheme to get a link relative to the
-   *   Drupal base path (like the <base> HTML element). For a link to an entity
-   *   you may use entity:{entity_type}/{entity_id} URIs.
+   *   The URI of the resource including the scheme. Several Drupal-specific
+   *   URI schemes are supported. See the documentation of each for details:
+   *   - Url::SCHEME_BASE
+   *   - Url::SCHEME_ENTITY
+   *   - Url::SCHEME_USER_PATH
+   *   - Url::SCHEME_ROUTE
    * @param array $options
    *   (optional) An associative array of additional URL options, with the
    *   following elements:
@@ -283,7 +451,7 @@ public static function fromUserInput($user_input, $options = []) {
    *
    * You can call access() on the returned object to do access checking.
    *
-   * @return \Drupal\Core\Url
+   * @return static
    *   A new Url object with properties depending on the URI scheme.
    *
    * @throws \InvalidArgumentException
@@ -291,6 +459,9 @@ public static function fromUserInput($user_input, $options = []) {
    *
    * @see \Drupal\Core\Url::fromRoute()
    * @see \Drupal\Core\Url::fromUserInput()
+   * @see \Drupal\Core\Url::fromBaseRelativePath()
+   * @see \Drupal\Core\Url::fromHostRelativePath()
+   * @see \Drupal\Core\Entity\EntityInterface::url()
    */
   public static function fromUri($uri, $options = []) {
     $uri_parts = parse_url($uri);
@@ -320,18 +491,18 @@ public static function fromUri($uri, $options = []) {
       unset($uri_parts['query']);
     }
 
-    if ($uri_parts['scheme'] === 'entity') {
+    if ($uri_parts['scheme'] === static::SCHEME_ENTITY) {
       $url = static::fromEntityUri($uri_parts, $uri_options, $uri);
     }
     elseif ($uri_parts['scheme'] === 'internal') {
       $url = static::fromInternalUri($uri_parts, $uri_options);
     }
-    elseif ($uri_parts['scheme'] === 'route') {
+    elseif ($uri_parts['scheme'] === static::SCHEME_ROUTE) {
       $url = static::fromRouteUri($uri_parts, $uri_options, $uri);
     }
     else {
       $url = new static($uri, array(), $options);
-      if ($uri_parts['scheme'] !== 'base') {
+      if ($uri_parts['scheme'] !== static::SCHEME_BASE) {
         $url->external = TRUE;
         $url->setOption('external', TRUE);
       }
@@ -348,11 +519,11 @@ public static function fromUri($uri, $options = []) {
    *   Parts from an URI of the form entity:{entity_type}/{entity_id} as from
    *   parse_url().
    * @param array $options
-   *   An array of options, see static::fromUri() for details.
+   *   An array of options, see Url::fromUri() for details.
    * @param string $uri
    *   The original entered URI.
    *
-   * @return \Drupal\Core\Url
+   * @return static
    *   A new Url object for an entity's canonical route.
    *
    * @throws \InvalidArgumentException
@@ -360,8 +531,9 @@ public static function fromUri($uri, $options = []) {
    */
   protected static function fromEntityUri(array $uri_parts, array $options, $uri) {
     list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2);
-    if ($uri_parts['scheme'] != 'entity' || $entity_id === '') {
-      throw new \InvalidArgumentException("The entity URI '$uri' is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.");
+    if ($uri_parts['scheme'] !== static::SCHEME_ENTITY || $entity_id === '') {
+      // @todo Update this exception for the new constant.
+      throw new \InvalidArgumentException(SafeMarkup::format('The entity URI "@uri" is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.', ['@uri' => $uri]));
     }
 
     return new static("entity.$entity_type_id.canonical", [$entity_type_id => $entity_id], $options);
@@ -432,7 +604,7 @@ protected static function fromInternalUri(array $uri_parts, array $options) {
     }
 
     $url = \Drupal::pathValidator()
-      ->getUrlIfValidWithoutAccessCheck($uri_parts['path']) ?: static::fromUri('base:' . $uri_parts['path'], $options);
+      ->getUrlIfValidWithoutAccessCheck($uri_parts['path']) ?: static::fromUri(static::SCHEME_BASE . ':' . $uri_parts['path'], $options);
     // Allow specifying additional options.
     $url->setOptions($options + $url->getOptions());
 
@@ -447,11 +619,11 @@ protected static function fromInternalUri(array $uri_parts, array $options) {
    *   from parse_url(), where the path is the route name optionally followed by
    *   a ";" followed by route parameters in key=value format with & separators.
    * @param array $options
-   *   An array of options, see static::fromUri() for details.
+   *   An array of options. See Url::fromUri() for details.
    * @param string $uri
    *   The original passed in URI.
    *
-   * @return \Drupal\Core\Url
+   * @return static
    *   A new Url object for a 'route:' URI.
    *
    * @throws \InvalidArgumentException
@@ -461,6 +633,7 @@ protected static function fromRouteUri(array $uri_parts, array $options, $uri) {
     $route_parts = explode(';', $uri_parts['path'], 2);
     $route_name = $route_parts[0];
     if ($route_name === '') {
+      // @todo Update this exception for the new constant.
       throw new \InvalidArgumentException("The route URI '$uri' is invalid. You must have a route name in the URI. e.g., route:system.admin");
     }
     $route_parameters = [];
@@ -510,7 +683,6 @@ public static function createFromRequest(Request $request) {
   protected function setUnrouted() {
     $this->unrouted = TRUE;
     // What was passed in as the route name is actually the URI.
-    // @todo Consider fixing this in https://www.drupal.org/node/2346787.
     $this->uri = $this->routeName;
     // Set empty route name and parameters.
     $this->routeName = NULL;
@@ -527,10 +699,12 @@ protected function setUnrouted() {
    *
    * @return string
    *   A URI representation of the Url object data.
+   *
+   * @todo Update this documentation for the new constants and methods.
    */
   public function toUriString() {
     if ($this->isRouted()) {
-      $uri = 'route:' . $this->routeName;
+      $uri = static::SCHEME_ROUTE . ':' . $this->routeName;
       if ($this->routeParameters) {
         $uri .= ';' . UrlHelper::buildQuery($this->routeParameters);
       }
@@ -739,6 +913,8 @@ public function setAbsolute($absolute = TRUE) {
    *   A string URL.
    *   When $collect_bubbleable_metadata is TRUE, a GeneratedUrl object is
    *   returned, containing the generated URL plus bubbleable metadata.
+   *
+   * @todo Update this documentation for the new constants and methods.
    */
   public function toString($collect_bubbleable_metadata = FALSE) {
     if ($this->unrouted) {
diff --git a/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php b/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php
index 3c97c44..d15340a 100644
--- a/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php
+++ b/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php
@@ -111,9 +111,7 @@ protected function buildLocalUrl($uri, array $options = [], $collect_bubbleable_
     $request = $this->requestStack->getCurrentRequest();
 
     // Remove the base: scheme.
-    // @todo Consider using a class constant for this in
-    //   https://www.drupal.org/node/2417459
-    $uri = substr($uri, 5);
+    $uri = substr($uri, strlen(Url::SCHEME_BASE) + 1);
 
     // Strip leading slashes from internal paths to prevent them becoming
     // external URLs without protocol. /example.com should not be turned into
diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php
index 635eec3..bc0473d 100644
--- a/core/tests/Drupal/Tests/Core/UrlTest.php
+++ b/core/tests/Drupal/Tests/Core/UrlTest.php
@@ -786,6 +786,66 @@ public function testFromRouteUriWithMissingRouteName() {
   }
 
   /**
+   * Tests the fromBaseRelativePath()
+   *
+   * @covers ::fromBaseRelativePath
+   *
+   * @dataProvider providerTestFromBaseRelativePath
+   */
+  public function testFromBaseRelativePath($path, $options, $uri_string) {
+    $url = Url::fromBaseRelativePath($path, $options);
+
+    $this->assertFalse($url->isRouted());
+    $this->assertSame(Url::SCHEME_BASE, parse_url($url->toUriString(), PHP_URL_SCHEME));
+    $this->assertSame($uri_string, $url->toUriString());
+  }
+
+  /**
+   *  Data provider for testing base relative paths.
+   */
+  public function providerTestFromBaseRelativePath() {
+    return [
+      ['robots.txt', [], Url::SCHEME_BASE . ':robots.txt'],
+      ['/robots.txt', [], Url::SCHEME_BASE . ':robots.txt'],
+      ['//robots.txt', [], Url::SCHEME_BASE . ':robots.txt'],
+      ['/blog/cats?foo=bar', [], Url::SCHEME_BASE . ':blog/cats?foo=bar'],
+      ['/blog/cats', ['query'=>['foo' => 'bar']], Url::SCHEME_BASE . ':blog/cats?foo=bar'],
+      ['/blog/cats#foo', [], Url::SCHEME_BASE . ':blog/cats#foo'],
+      ['/blog/cats', ['fragment'=>'foo'], Url::SCHEME_BASE . ':blog/cats#foo'],
+    ];
+  }
+
+  /**
+   * Tests the fromHostRelativePath()
+   *
+   * @covers ::fromHostRelativePath
+   *
+   * @dataProvider providerTestFromHostRelativePath
+   */
+  public function testFromHostRelativePath($path, $options, $uri_string) {
+    $url = Url::fromHostRelativePath($path, $options);
+
+    $this->assertFalse($url->isRouted());
+    $this->assertSame(Url::SCHEME_BASE, parse_url($url->toUriString(), PHP_URL_SCHEME));
+    $this->assertSame($uri_string, $url->toUriString());
+  }
+
+  /**
+   *  Data provider for testing base relative paths.
+   */
+  public function providerTestFromHostRelativePath() {
+    return [
+      ['index.html', [], Url::SCHEME_BASE . ':/index.html'],
+      ['/index.html', [], Url::SCHEME_BASE . ':/index.html'],
+      ['//index.html', [], Url::SCHEME_BASE . ':/index.html'],
+      ['/blog/cats?foo=bar', [], Url::SCHEME_BASE . ':/blog/cats?foo=bar'],
+      ['/blog/cats', ['query'=>['foo' => 'bar']], Url::SCHEME_BASE . ':/blog/cats?foo=bar'],
+      ['/blog/cats#foo', [], Url::SCHEME_BASE . ':/blog/cats#foo'],
+      ['/blog/cats', ['fragment'=>'foo'], Url::SCHEME_BASE . ':/blog/cats#foo'],
+    ];
+  }
+
+  /**
    * Creates a mock access manager for the access tests.
    *
    * @param bool $access
