diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 34187f2..be4d953 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -592,7 +592,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) {
         // Save $_SESSION data from batch.
diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index 6a8c17a..7525374 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -19,11 +19,114 @@
 
 /**
  * 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() would return
+   *   '/drupal/README.txt'.
+   * - Url::fromUri(Url::SCHEME_BASE . ':/index.html')->toString() would 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:
+   * - <a href="README.txt"></a> (browser resolves as '/drupal/README.txt')
+   * - <a href="/index.html"></a> (browser resolves as '/index.html')
+   *
+   * The examples above can also be generated from the wrapper methods
+   * fromBaseRelativePath() and fromHostRelativePath(), as follows:
+   * - Url::fromBaseRelativePath('README.txt') is equivalent to
+   *   Url::fromUri(Url::SCHEME_BASE . ':README.txt').
+   * - Url::fromHostRelativePath('index.html') is equivalent to
+   *   Url::fromUri(Url::SCHEME_BASE . ':/index.html').
+   *
+   * Do not use this scheme for linking to pages generated by Drupal. With this
+   * scheme, mo path processors are applied (including language prefixes and
+   * aliases) and no access checking is performed. This means it is
+   * incompatible with:
+   * - Multilingual sites
+   * - Search engine optimization
+   * - Hiding links the user cannot access
+   * 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 URIs from user input.
+   *
+   * To create a URL for paths from user input that may or may not correspond
+   * to a valid Drupal route, use Url::fromUserInput().
+   *
+   * @see \Drupal\Core\Url::fromUserInput()
+   * @see \Drupal\Core\Url::fromUserPathUri()
+   *
+   * @todo Rename this constant depending on the outcome of:
+   *   https://www.drupal.org/node/2417567
+   */
+   const SCHEME_USER_PATH = 'user-path';
+
+  /**
+   * 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
@@ -45,7 +148,7 @@ class Url {
   protected $accessManager;
 
   /**
-   * The route name.
+   * The route name for the URL, if any.
    *
    * @var string
    */
@@ -98,12 +201,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
@@ -122,12 +231,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;
@@ -138,9 +241,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
@@ -166,19 +269,30 @@ public function __construct($route_name, $route_parameters = array(), $options =
    * @return \Drupal\Core\Url
    *   A new Url object for a routed (internal to Drupal) URL.
    *
-   * @see static::fromUri()
+   * @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()) {
     return new static($route_name, $route_parameters, $options);
   }
 
   /**
-   * 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
+   *
+   * @see \Drupal\Core\Url::fromRoute()
+   * @see \Drupal\Core\Routing\RouteMatchInterface
    */
   public static function fromRouteMatch(RouteMatchInterface $route_match) {
     if ($route_match->getRouteObject()) {
@@ -190,26 +304,141 @@ 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 \Drupal\Core\Url
+   *   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 = static::trimLeadingSlashes($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 \Drupal\Core\Url
+   *   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 = static::trimLeadingSlashes($path);
+    return static::fromUri(static::SCHEME_BASE . ':/' . $path, $options);
+  }
+
+  /**
+   * Trims leading slashes from a path.
+   *
+   * @param string $path
+   *   A potential base-relative or host-relative path.
+   *
+   * @return string
+   *   The path, with leading slashes trimmed.
+   */
+  protected static function trimLeadingSlashes($path) {
+    return preg_replace('/^(\/)+/', '', $path);
+  }
+
+  /**
+   * Creates a Url object for a path from user input.
+   *
+   * Use this method to create a URL for paths from user input that may or may
+   * not correspond to a valid Drupal route.
+   *
+   * @param string $path
+   *   A path string from user input. Only three types of paths are allowed:
+   *   - Site paths, beginning with a '/'.
+   *   - Query strings, beginning with a '?'.
+   *   - Fragments, beginning with a '#'.
+   * @param array $options
+   *   (optional) An array of options. See Url::fromUri() for details.
+   *
+   * @return \Drupal\Core\Url
+   *   A new Url object based on user input.
+   *
+   * @todo The intent here is to support a user interaction pattern where
+   *   entering one of the three allowed characters at the beginning of a
+   *   field denotes that the user wants to link to a specific path on the
+   *   site, with all other input interpreted as an attempt to autocomplete the
+   *   label of a specific resource. How do we document that, and how do we
+   *   establish the expectation for other APIs and for UIs?
+   */
+  public static function fromUserInput($path, $options = []) {
+    if ((strpos($path, '/') !== 0) && (strpos($path, '/') !== 0) && (strpos($path, '/') !== 0)) {
+      throw new \InvalidArgumentException(String::format('The path @path is not valid as user input. User-input paths must begin with one of the following characters: / # ?', ['@path' => $path]));
+    }
+    return static::fromUri(static::SCHEME_USER_PATH . ':' . $path, $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 URLs that have Drupal routes (that is, most pages generated by Drupal),
-   * use Url::fromRoute().
+   * - 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().
+   *
+   * 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 user-path: 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:
@@ -227,19 +456,17 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
    *     defined, the current scheme is used, so the user stays on HTTP or HTTPS
    *     respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
    *
-   * Note: the user-path: scheme should be avoided except when processing actual
-   * user input that may or may not correspond to a Drupal route. Normally use
-   * Url::fromRoute() for code linking to any any Drupal page.
-   *
-   * You can call access() on the returned object to do access checking.
-   *
    * @return \Drupal\Core\Url
    *   A new Url object with properties depending on the URI scheme.
    *
    * @throws \InvalidArgumentException
    *   Thrown when the passed in path has no scheme.
    *
-   * @see static::fromRoute()
+   * @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);
@@ -247,7 +474,7 @@ public static function fromUri($uri, $options = []) {
       throw new \InvalidArgumentException(String::format('The URI "@uri" is malformed.', ['@uri' => $uri]));
     }
     if (empty($uri_parts['scheme'])) {
-      throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base: for items like a static file that needs the base path. Use user-path: for user input without a scheme. Use entity: for referencing the canonical route of a content entity. Use route: for directly representing a route name and parameters.', ['@uri' => $uri]));
+      throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme.', ['@uri' => $uri]));
     }
     $uri_parts += ['path' => ''];
     // Discard empty fragment in $options for consistency with parse_url().
@@ -269,18 +496,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'] === 'user-path') {
+    elseif ($uri_parts['scheme'] === static::SCHEME_USER_PATH) {
       $url = static::fromUserPathUri($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);
       }
@@ -297,7 +524,7 @@ 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.
    *
@@ -309,7 +536,8 @@ 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 === '') {
+    if ($uri_parts['scheme'] !== static::SCHEME_ENTITY || $entity_id === '') {
+      // @todo Update this exception for the new constant.
       throw new \InvalidArgumentException(String::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]));
     }
 
@@ -349,10 +577,13 @@ protected static function fromEntityUri(array $uri_parts, array $options, $uri)
    * @param array $uri_parts
    *   Parts from an URI of the form user-path:{path} 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.
    *
    * @return \Drupal\Core\Url
    *   A new Url object for a 'user-path:' URI.
+   *
+   * @todo Rename this method depending on the outcome of:
+   *   https://www.drupal.org/node/2417567
    */
   protected static function fromUserPathUri(array $uri_parts, array $options) {
     // Both PathValidator::getUrlIfValidWithoutAccessCheck() and 'base:' URIs
@@ -371,7 +602,7 @@ protected static function fromUserPathUri(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());
 
@@ -386,7 +617,7 @@ protected static function fromUserPathUri(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.
    *
@@ -400,6 +631,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(String::format('The route URI "@uri" is invalid. You must have a route name in the URI. e.g., route:system.admin', ['@uri' => $uri]));
     }
     $route_parameters = [];
@@ -449,7 +681,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;
@@ -466,10 +697,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);
       }
@@ -672,6 +905,8 @@ public function setAbsolute($absolute = TRUE) {
    *
    * @return string
    *   A string URL.
+   *
+   * @todo Update this documentation for the new constants and methods.
    */
   public function toString() {
     if ($this->unrouted) {
