diff --git a/core/includes/batch.inc b/core/includes/batch.inc
index 362854d..7d905cd 100644
--- a/core/includes/batch.inc
+++ b/core/includes/batch.inc
@@ -153,10 +153,12 @@ function _batch_progress_page() {
 
   // Merge required query parameters for batch processing into those provided by
   // batch_set() or hook_batch_alter().
-  $batch['url_options']['query']['id'] = $batch['id'];
-  $batch['url_options']['query']['op'] = $new_op;
+  $query_options = $batch['url']->getOption('query');
+  $query_options['id'] = $batch['id'];
+  $query_options['op'] = $new_op;
+  $batch['url']->setOption('query', $query_options);
 
-  $url = _url($batch['url'], $batch['url_options']);
+  $url = $batch['url']->toString();
 
   $build = array(
     '#theme' => 'progress_bar',
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 69c970f..4cd1b39 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -747,7 +747,7 @@ function batch_set($batch_definition) {
  *
  * @param $redirect
  *   (optional) Path to redirect to when the batch has finished processing.
- * @param $url
+ * @param \Drupal\Core\Url $url
  *   (optional - should only be used for separate scripts like update.php)
  *   URL of the batch processing page.
  * @param $redirect_callback
@@ -757,7 +757,7 @@ function batch_set($batch_definition) {
  * @return \Symfony\Component\HttpFoundation\RedirectResponse|null
  *   A redirect response if the batch is progressive. No return value otherwise.
  */
-function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NULL) {
+function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL) {
   $batch =& batch_get();
 
   if (isset($batch)) {
@@ -765,8 +765,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NU
     $process_info = array(
       'current_set' => 0,
       'progressive' => TRUE,
-      'url' => $url,
-      'url_options' => array(),
+      'url' => isset($url) ? $url : Url::fromRoute('system.batch_page.html'),
       'source_url' => Url::fromRouteMatch(\Drupal::routeMatch()),
       'batch_redirect' => $redirect,
       'theme' => \Drupal::theme()->getActiveTheme()->getName(),
@@ -793,7 +792,16 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NU
     if ($batch['progressive']) {
       // Now that we have a batch id, we can generate the redirection link in
       // the generic error message.
-      $batch['error_message'] = t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => _url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
+      /** @var \Drupal\Core\Url $batch_url */
+      $batch_url = $batch['url'];
+      /** @var \Drupal\Core\Url $error_url */
+      $error_url = clone $batch_url;
+      $query_options = $error_url->getOption('query');
+      $query_options['id'] = $batch['id'];
+      $query_options['op'] = 'finished';
+      $error_url->setOption('query', $query_options);
+
+      $batch['error_message'] = t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => $error_url->toString()));
 
       // Clear the way for the redirection to the batch processing page, by
       // saving and unsetting the 'destination', if there is any.
@@ -815,13 +823,16 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NU
       $_SESSION['batches'][$batch['id']] = TRUE;
 
       // Redirect for processing.
-      $options = array('query' => array('op' => 'start', 'id' => $batch['id']));
+      $query_options = $error_url->getOption('query');
+      $query_options['op'] = 'start';
+      $query_options['id'] = $batch['id'];
+      $batch_url->setOption('query', $query_options);
       if (($function = $batch['redirect_callback']) && function_exists($function)) {
-        $function($batch['url'], $options);
+        $function($batch_url, ['query' => $query_options]);
       }
       else {
-        $options['absolute'] = TRUE;
-        return new RedirectResponse(_url($batch['url'], $options));
+        $batch_url->setAbsolute();
+        return new RedirectResponse($batch_url->toString());
       }
     }
     else {
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index d2c1312..7784fd9 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -18,6 +18,7 @@
 use Drupal\Core\StringTranslation\Translator\FileTranslation;
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Url;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\HttpFoundation\Request;
@@ -578,7 +579,7 @@ function install_run_task($task, &$install_state) {
       // install_redirect_url() returns core/install.php, so let's ensure to
       // drop it from it and use base:// as batch_process() is using the
       // unrouted URL assembler, which requires base://.
-      $response = batch_process(preg_replace('@^core/@', 'base://', install_redirect_url($install_state)), install_full_redirect_url($install_state));
+      $response = batch_process(preg_replace('@^core/@', 'base://', install_redirect_url($install_state)), Url::fromUri('base://install.php', ['query' => $install_state['parameters']]));
       if ($response instanceof Response) {
         // Save $_SESSION data from batch.
         \Drupal::service('session_manager')->save();
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
index aa97891..44063ad 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Field\Plugin\Field\FieldType;
 
+use Drupal\Core\Config\Entity\ConfigEntityType;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\TypedData\EntityDataDefinition;
@@ -145,27 +146,12 @@ public function setValue($values, $notify = TRUE) {
       $this->set('entity', $values, $notify);
     }
     else {
-      parent::setValue($values, FALSE);
-      // Support setting the field item with only one property, but make sure
-      // values stay in sync if only property is passed.
+      // Make sure that the 'entity' property gets set as 'target_id'.
       if (isset($values['target_id']) && !isset($values['entity'])) {
-        $this->onChange('target_id', FALSE);
-      }
-      elseif (!isset($values['target_id']) && isset($values['entity'])) {
-        $this->onChange('entity', FALSE);
-      }
-      elseif (isset($values['target_id']) && isset($values['entity'])) {
-        // If both properties are passed, verify the passed values match.
-        if ($this->get('entity')->getTargetIdentifier() != $values['target_id']) {
-          throw new \InvalidArgumentException('The target id and entity passed to the entity reference item do not match.');
-        }
-      }
-      // Notify the parent if necessary.
-      if ($notify && $this->parent) {
-        $this->parent->onChange($this->getName());
+        $values['entity'] = $values['target_id'];
       }
+      parent::setValue($values, $notify);
     }
-
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index c0c982a..0283333 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -291,9 +291,8 @@ public static function preRenderAjaxForm($element) {
         $settings['progress'] = array('type' => $settings['progress']);
       }
       // Change progress path to a full URL.
-      if (isset($settings['progress']['path'])) {
-        $settings['progress']['url'] = _url($settings['progress']['path']);
-        unset($settings['progress']['path']);
+      if (isset($settings['progress']['url']) && $settings['progress']['url'] instanceof Url) {
+        $settings['progress']['url'] = $settings['progress']['url']->toString();
       }
 
       $element['#attached']['drupalSettings']['ajax'][$element['#id']] = $settings;
diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index 999f447..4dde31a 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -470,7 +470,13 @@ public function toString() {
       return $this->unroutedUrlAssembler()->assemble($this->getUri(), $this->getOptions());
     }
 
-    return $this->urlGenerator()->generateFromRoute($this->getRouteName(), $this->getRouteParameters(), $this->getOptions());
+    try {
+      return $this->urlGenerator()->generateFromRoute($this->getRouteName(), $this->getRouteParameters(), $this->getOptions());
+    }
+    catch (\Exception $e) {
+      debug($e->getMessage());
+      return '';
+    }
   }
 
   /**
diff --git a/core/modules/aggregator/src/Tests/FeedParserTest.php b/core/modules/aggregator/src/Tests/FeedParserTest.php
index ab6aaaf..46b1095 100644
--- a/core/modules/aggregator/src/Tests/FeedParserTest.php
+++ b/core/modules/aggregator/src/Tests/FeedParserTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\aggregator\Tests;
 
+use Drupal\Core\Url;
 use Zend\Feed\Reader\Reader;
 
 /**
@@ -80,7 +81,7 @@ function testHtmlEntitiesSample() {
    */
   function testRedirectFeed() {
     // Simulate a typo in the URL to force a curl exception.
-    $invalid_url = _url('aggregator/redirect', array('absolute' => TRUE));
+    $invalid_url = Url::fromUri('base://' . 'aggregator/redirect', array('absolute' => TRUE))->toString();
     $feed = entity_create('aggregator_feed', array('url' => $invalid_url, 'title' => $this->randomMachineName()));
     $feed->save();
     $feed->refreshItems();
diff --git a/core/modules/basic_auth/src/Tests/Authentication/BasicAuthTest.php b/core/modules/basic_auth/src/Tests/Authentication/BasicAuthTest.php
index 9c5375a..5289127 100644
--- a/core/modules/basic_auth/src/Tests/Authentication/BasicAuthTest.php
+++ b/core/modules/basic_auth/src/Tests/Authentication/BasicAuthTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\basic_auth\Tests\Authentication;
 
+use Drupal\Core\Url;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\simpletest\WebTestBase;
 
@@ -150,7 +151,7 @@ protected function basicAuthGet($path, $username, $password) {
     $out = $this->curlExec(
       array(
         CURLOPT_HTTPGET => TRUE,
-        CURLOPT_URL => _url($path, array('absolute' => TRUE)),
+        CURLOPT_URL => Url::fromUri('base://' . $path, array('absolute' => TRUE))->toString(),
         CURLOPT_NOBODY => FALSE,
         CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
         CURLOPT_USERPWD => $username . ':' . $password,
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 4978551..320ea19 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -311,16 +311,17 @@ function comment_view_multiple($comments, $view_mode = 'full', $langcode = NULL)
 }
 
 /**
- * Implements hook_form_FORM_ID_alter() for field_ui_field_storage_add_form.
+ * Implements hook_form_FORM_ID_alter() for field_ui_field_overview_form.
  */
-function comment_form_field_ui_field_storage_add_form_alter(&$form, FormStateInterface $form_state) {
+function comment_form_field_ui_field_overview_form_alter(&$form, FormStateInterface $form_state) {
   $request = \Drupal::request();
-  if ($form_state->get('entity_type_id') == 'comment' && $request->attributes->has('commented_entity_type')) {
+  if ($form['#entity_type'] == 'comment' && $request->attributes->has('commented_entity_type')) {
     $form['#title'] = \Drupal::service('comment.manager')->getFieldUIPageTitle($request->attributes->get('commented_entity_type'), $request->attributes->get('field_name'));
   }
-  if (!_comment_entity_uses_integer_id($form_state->get('entity_type_id'))) {
+  $entity_type_id = $form['#entity_type'];
+  if (!_comment_entity_uses_integer_id($entity_type_id)) {
     // You cannot use comment fields on entity types with non-integer IDs.
-    unset($form['add']['new_storage_type']['#options']['comment']);
+    unset($form['fields']['_add_new_field']['type']['#options']['comment']);
   }
 }
 
diff --git a/core/modules/comment/src/Tests/CommentNonNodeTest.php b/core/modules/comment/src/Tests/CommentNonNodeTest.php
index 704a298..79a506c 100644
--- a/core/modules/comment/src/Tests/CommentNonNodeTest.php
+++ b/core/modules/comment/src/Tests/CommentNonNodeTest.php
@@ -408,24 +408,24 @@ public function testsNonIntegerIdEntities() {
       'administer entity_test_string_id fields',
     ));
     $this->drupalLogin($limited_user);
-    // Visit the Field UI field add page.
-    $this->drupalGet('entity_test_string_id/structure/entity_test/fields/add-field');
+    // Visit the Field UI overview.
+    $this->drupalGet('entity_test_string_id/structure/entity_test/fields');
     // Ensure field isn't shown for string IDs.
-    $this->assertNoOption('edit-new-storage-type', 'comment');
+    $this->assertNoOption('edit-fields-add-new-field-type', 'comment');
     // Ensure a core field type shown.
-    $this->assertOption('edit-new-storage-type', 'boolean');
+    $this->assertOption('edit-fields-add-new-field-type', 'boolean');
 
     // Create a bundle for entity_test_no_id.
     entity_test_create_bundle('entity_test', 'Entity Test', 'entity_test_no_id');
     $this->drupalLogin($this->drupalCreateUser(array(
       'administer entity_test_no_id fields',
     )));
-    // Visit the Field UI field add page.
-    $this->drupalGet('entity_test_no_id/structure/entity_test/fields/add-field');
+    // Visit the Field UI overview.
+    $this->drupalGet('entity_test_no_id/structure/entity_test/fields');
     // Ensure field isn't shown for empty IDs.
-    $this->assertNoOption('edit-new-storage-type', 'comment');
+    $this->assertNoOption('edit-fields-add-new-field-type', 'comment');
     // Ensure a core field type shown.
-    $this->assertOption('edit-new-storage-type', 'boolean');
+    $this->assertOption('edit-fields-add-new-field-type', 'boolean');
   }
 
 }
diff --git a/core/modules/contact/src/Tests/ContactSitewideTest.php b/core/modules/contact/src/Tests/ContactSitewideTest.php
index 66176c4..04669f3 100644
--- a/core/modules/contact/src/Tests/ContactSitewideTest.php
+++ b/core/modules/contact/src/Tests/ContactSitewideTest.php
@@ -247,8 +247,6 @@ function testSiteWideContact() {
 
     $this->clickLink(t('Manage fields'), $i);
     $this->assertResponse(200);
-    $this->clickLink(t('Add field'));
-    $this->assertResponse(200);
 
     // Create a simple textfield.
     $field_name = Unicode::strtolower($this->randomMachineName());
diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
index a13a75b..2b54597 100644
--- a/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
+++ b/core/modules/entity_reference/src/Tests/EntityReferenceAdminTest.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\entity_reference\Tests;
 
-use Drupal\field_ui\Tests\FieldUiTestTrait;
 use Drupal\simpletest\WebTestBase;
 use Drupal\taxonomy\Entity\Vocabulary;
 
@@ -18,8 +17,6 @@
  */
 class EntityReferenceAdminTest extends WebTestBase {
 
-  use FieldUiTestTrait;
-
   /**
    * Modules to install.
    *
@@ -58,11 +55,11 @@ public function testFieldAdminHandler() {
     $bundle_path = 'admin/structure/types/manage/' . $this->type;
 
     // First step: 'Add new field' on the 'Manage fields' page.
-    $this->drupalPostForm($bundle_path . '/fields/add-field', array(
-      'label' => 'Test label',
-      'field_name' => 'test',
-      'new_storage_type' => 'entity_reference',
-    ), t('Save and continue'));
+    $this->drupalPostForm($bundle_path . '/fields', array(
+      'fields[_add_new_field][label]' => 'Test label',
+      'fields[_add_new_field][field_name]' => 'test',
+      'fields[_add_new_field][type]' => 'entity_reference',
+    ), t('Save'));
 
     // Node should be selected by default.
     $this->assertFieldByName('field_storage[settings][target_type]', 'node');
@@ -201,13 +198,24 @@ public function createEntityReferenceField($target_type, $bundle = NULL) {
     // Generate a random field name, must be only lowercase characters.
     $field_name = strtolower($this->randomMachineName());
 
-    $storage_edit = $field_edit = array();
-    $storage_edit['field_storage[settings][target_type]'] = $target_type;
-    if ($bundle) {
-      $field_edit['field[settings][handler_settings][target_bundles][' . $bundle . ']'] = TRUE;
+    // Create the initial entity reference.
+    $this->drupalPostForm($bundle_path . '/fields', array(
+      'fields[_add_new_field][label]' => $this->randomMachineName(),
+      'fields[_add_new_field][field_name]' => $field_name,
+      'fields[_add_new_field][type]' => 'entity_reference',
+    ), t('Save'));
+
+    // Select the correct target type given in the parameters and save field settings.
+    $this->drupalPostForm(NULL, array('field_storage[settings][target_type]' => $target_type), t('Save field settings'));
+
+    // Select required fields if there are any.
+    $edit = array();
+    if($bundle) {
+      $edit['field[settings][handler_settings][target_bundles][' . $bundle . ']'] = TRUE;
     }
 
-    $this->fieldUIAddNewField($bundle_path, $field_name, NULL, 'entity_reference', $storage_edit, $field_edit);
+    // Save settings.
+    $this->drupalPostForm(NULL, $edit, t('Save settings'));
 
     // Returns the generated field name.
     return $field_name;
diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceItemTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceItemTest.php
index c7fc90b..db91b59 100644
--- a/core/modules/entity_reference/src/Tests/EntityReferenceItemTest.php
+++ b/core/modules/entity_reference/src/Tests/EntityReferenceItemTest.php
@@ -105,33 +105,10 @@ public function testContentEntityReferenceItem() {
     ));
     $term2->save();
 
-    // Test all the possible ways of assigning a value.
-    $entity->field_test_taxonomy_term->target_id = $term->id();
-    $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
-    $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term->getName());
-
-    $entity->field_test_taxonomy_term = [['target_id' => $term2->id()]];
+    $entity->field_test_taxonomy_term->target_id = $term2->id();
     $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term2->id());
     $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term2->getName());
 
-    // Test value assignment via the computed 'entity' property.
-    $entity->field_test_taxonomy_term->entity = $term;
-    $this->assertEqual($entity->field_test_taxonomy_term->target_id, $term->id());
-    $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term->getName());
-
-    $entity->field_test_taxonomy_term = [['entity' => $term2]];
-    $this->assertEqual($entity->field_test_taxonomy_term->target_id, $term2->id());
-    $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term2->getName());
-
-    // Test assigning an invalid item throws an exception.
-    try {
-      $entity->field_test_taxonomy_term = ['target_id' => 'invalid', 'entity' => $term2];
-      $this->fail('Assigning an invalid item throws an exception.');
-    }
-    catch (\InvalidArgumentException $e) {
-      $this->pass('Assigning an invalid item throws an exception.');
-    }
-
     // Delete terms so we have nothing to reference and try again
     $term->delete();
     $term2->delete();
diff --git a/core/modules/field_ui/css/field_ui.admin.css b/core/modules/field_ui/css/field_ui.admin.css
index f85e101..29bef62 100644
--- a/core/modules/field_ui/css/field_ui.admin.css
+++ b/core/modules/field_ui/css/field_ui.admin.css
@@ -3,28 +3,37 @@
  * Stylesheet for the Field UI module.
  */
 
-/* Add new field page. */
-.field-ui-field-storage-add-form .field-type-wrapper .form-item {
-  float: left;
-  margin-right: 1em;
-  vertical-align: text-bottom;
+/* 'Manage fields' and 'Manage display' overviews */
+.field-ui-overview .add-new .label-input {
+  float: left; /* LTR */
 }
-[dir="rtl"] .field-ui-field-storage-add-form .field-type-wrapper .form-item {
+[dir="rtl"] .field-ui-overview .add-new .label-input {
   float: right;
-  margin-left: 1em;
-  margin-right: 0;
 }
-.field-ui-field-storage-add-form .field-type-wrapper .form-item-separator {
-  margin-top: 2.3em;
+.field-ui-overview .add-new .description {
+  margin-bottom: 0;
+  max-width: 250px;
+}
+.field-ui-overview .add-new .form-type-machine-name .description {
+  white-space: normal;
+}
+.field-ui-overview .add-new .add-new-placeholder {
+  font-weight: bold;
+  padding-bottom: .5em;
 }
-
-/* 'Manage fields' and 'Manage display' overviews */
 .field-ui-overview .region-title td {
   font-weight: bold;
 }
 .field-ui-overview .region-message td {
   font-style: italic;
 }
+.field-ui-overview .region-add-new-title {
+  display: none;
+}
+.field-ui-overview .add-new td {
+  vertical-align: top;
+  white-space: nowrap;
+}
 
 /* 'Manage form display' and 'Manage display' overview */
 .field-ui-overview .field-plugin-summary-cell {
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index 9d0e4b4..29da4a3 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -3,7 +3,7 @@
  * Attaches the behaviors for the Field UI module.
  */
 
-(function ($, Drupal, drupalSettings) {
+(function ($) {
 
   "use strict";
 
@@ -294,4 +294,4 @@
     }
   };
 
-})(jQuery, Drupal, drupalSettings);
+})(jQuery);
diff --git a/core/modules/field_ui/field_ui.links.action.yml b/core/modules/field_ui/field_ui.links.action.yml
index b03bac5..dae1583 100644
--- a/core/modules/field_ui/field_ui.links.action.yml
+++ b/core/modules/field_ui/field_ui.links.action.yml
@@ -11,7 +11,3 @@ field_ui.entity_form_mode_add:
   weight: 1
   appears_on:
     - field_ui.entity_form_mode_list
-
-field_ui.field_storage_config_add:
-  class: \Drupal\Core\Menu\LocalActionDefault
-  deriver: \Drupal\field_ui\Plugin\Derivative\FieldUiLocalAction
diff --git a/core/modules/field_ui/src/Controller/FieldConfigListController.php b/core/modules/field_ui/src/Controller/FieldConfigListController.php
deleted file mode 100644
index 8ed9be0..0000000
--- a/core/modules/field_ui/src/Controller/FieldConfigListController.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\field_ui\Controller\FieldConfigListController.
- */
-
-namespace Drupal\field_ui\Controller;
-
-use Drupal\Core\Entity\Controller\EntityListController;
-use Symfony\Component\HttpFoundation\Request;
-
-/**
- * Defines a controller to list field instances.
- */
-class FieldConfigListController extends EntityListController {
-
-  /**
-   * Shows the 'Manage fields' page.
-   *
-   * @param string $entity_type_id
-   *   The entity type.
-   * @param string $bundle
-   *   The entity bundle.
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The current request.
-   *
-   * @return array
-   *   A render array as expected by drupal_render().
-   */
-  public function listing($entity_type_id = NULL, $bundle = NULL, Request $request = NULL) {
-    if (!$bundle) {
-      $entity_info = $this->entityManager()->getDefinition($entity_type_id);
-      $bundle = $request->attributes->get('_raw_variables')->get($entity_info->getBundleEntityType());
-    }
-    return $this->entityManager()->getListBuilder('field_config')->render($entity_type_id, $bundle, $request);
-  }
-
-}
diff --git a/core/modules/field_ui/src/DisplayOverview.php b/core/modules/field_ui/src/DisplayOverview.php
index b302529..96d6a9f 100644
--- a/core/modules/field_ui/src/DisplayOverview.php
+++ b/core/modules/field_ui/src/DisplayOverview.php
@@ -204,7 +204,7 @@ protected function getTableHeader() {
    */
   protected function getOverviewRoute($mode) {
     return Url::fromRoute('field_ui.display_overview_view_mode_' . $this->entity_type, [
-      $this->bundleEntityTypeId => $this->bundle,
+      $this->bundleEntityType => $this->bundle,
       'view_mode_name' => $mode,
     ]);
   }
diff --git a/core/modules/field_ui/src/DisplayOverviewBase.php b/core/modules/field_ui/src/DisplayOverviewBase.php
index 09322d3..adab34e 100644
--- a/core/modules/field_ui/src/DisplayOverviewBase.php
+++ b/core/modules/field_ui/src/DisplayOverviewBase.php
@@ -9,7 +9,6 @@
 
 use Drupal\Component\Plugin\Factory\DefaultFactory;
 use Drupal\Component\Plugin\PluginManagerBase;
-use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\Display\EntityDisplayInterface;
@@ -17,51 +16,13 @@
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldTypePluginManagerInterface;
 use Drupal\Core\Field\PluginSettingsInterface;
-use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Render\Element;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Field UI display overview base class.
  */
-abstract class DisplayOverviewBase extends FormBase {
-
-  /**
-   * The name of the entity type.
-   *
-   * @var string
-   */
-  protected $entity_type = '';
-
-  /**
-   * The entity bundle.
-   *
-   * @var string
-   */
-  protected $bundle = '';
-
-  /**
-   * The name of the entity type which provides bundles for the entity type
-   * defined above.
-   *
-   * @var string
-   */
-  protected $bundleEntityTypeId;
-
-  /**
-   * The entity view or form mode.
-   *
-   * @var string
-   */
-  protected $mode = '';
-
-  /**
-   * The entity manager.
-   *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
-   */
-  protected $entityManager;
+abstract class DisplayOverviewBase extends OverviewBase {
 
   /**
    * The display context. Either 'view' or 'form'.
@@ -104,7 +65,8 @@
    *   The configuration factory.
    */
   public function __construct(EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager, ConfigFactoryInterface $config_factory) {
-    $this->entityManager = $entity_manager;
+    parent::__construct($entity_manager);
+
     $this->fieldTypes = $field_type_manager->getDefinitions();
     $this->pluginManager = $plugin_manager;
     $this->configFactory = $config_factory;
@@ -123,23 +85,7 @@ public static function create(ContainerInterface $container) {
   }
 
   /**
-   * Get the regions needed to create the overview form.
-   *
-   * @return array
-   *   Example usage:
-   *   @code
-   *     return array(
-   *       'content' => array(
-   *         // label for the region.
-   *         'title' => $this->t('Content'),
-   *         // Indicates if the region is visible in the UI.
-   *         'invisible' => TRUE,
-   *         // A message to indicate that there is nothing to be displayed in
-   *         // the region.
-   *         'message' => $this->t('No field is displayed.'),
-   *       ),
-   *     );
-   *   @endcode
+   * {@inheritdoc}
    */
   public function getRegions() {
     return array(
@@ -156,20 +102,6 @@ public function getRegions() {
   }
 
   /**
-   * Returns an associative array of all regions.
-   *
-   * @return array
-   *   An array containing the region options.
-   */
-  public function getRegionOptions() {
-    $options = array();
-    foreach ($this->getRegions() as $region => $data) {
-      $options[$region] = $data['title'];
-    }
-    return $options;
-  }
-
-  /**
    * Collects the definitions of fields whose display is configurable.
    *
    * @return \Drupal\Core\Field\FieldDefinitionInterface[]
@@ -186,16 +118,8 @@ protected function getFieldDefinitions() {
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL, $mode = 'default') {
-    $entity_type = $this->entityManager->getDefinition($entity_type_id);
-    $this->bundleEntityTypeId = $entity_type->getBundleEntityType();
-
-    if (!$form_state->get('bundle')) {
-      $bundle = $bundle ?: $this->getRequest()->attributes->get('_raw_variables')->get($this->bundleEntityTypeId);
-      $form_state->set('bundle', $bundle);
-    }
+    parent::buildForm($form, $form_state, $entity_type_id, $bundle);
 
-    $this->entity_type = $entity_type_id;
-    $this->bundle = $form_state->get('bundle');
     $this->mode = $mode;
 
     $field_definitions = $this->getFieldDefinitions();
@@ -766,120 +690,6 @@ public function multistepAjax($form, FormStateInterface $form_state) {
   }
 
   /**
-   * Performs pre-render tasks on field_ui_table elements.
-   *
-   * This function is assigned as a #pre_render callback in
-   * field_ui_element_info().
-   *
-   * @param array $elements
-   *   A structured array containing two sub-levels of elements. Properties
-   *   used:
-   *   - #tabledrag: The value is a list of $options arrays that are passed to
-   *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
-   *     $options array.
-   *
-   * @see drupal_render()
-   * @see \Drupal\Core\Render\Element\Table::preRenderTable()
-   */
-  public function tablePreRender($elements) {
-    $js_settings = array();
-
-    // For each region, build the tree structure from the weight and parenting
-    // data contained in the flat form structure, to determine row order and
-    // indentation.
-    $regions = $elements['#regions'];
-    $tree = array('' => array('name' => '', 'children' => array()));
-    $trees = array_fill_keys(array_keys($regions), $tree);
-
-    $parents = array();
-    $children = Element::children($elements);
-    $list = array_combine($children, $children);
-
-    // Iterate on rows until we can build a known tree path for all of them.
-    while ($list) {
-      foreach ($list as $name) {
-        $row = &$elements[$name];
-        $parent = $row['parent_wrapper']['parent']['#value'];
-        // Proceed if parent is known.
-        if (empty($parent) || isset($parents[$parent])) {
-          // Grab parent, and remove the row from the next iteration.
-          $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array();
-          unset($list[$name]);
-
-          // Determine the region for the row.
-          $region_name = call_user_func($row['#region_callback'], $row);
-
-          // Add the element in the tree.
-          $target = &$trees[$region_name][''];
-          foreach ($parents[$name] as $key) {
-            $target = &$target['children'][$key];
-          }
-          $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']);
-
-          // Add tabledrag indentation to the first row cell.
-          if ($depth = count($parents[$name])) {
-            $children = Element::children($row);
-            $cell = current($children);
-            $indentation = array(
-              '#theme' => 'indentation',
-              '#size' => $depth,
-            );
-            $row[$cell]['#prefix'] = drupal_render($indentation) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : '');
-          }
-
-          // Add row id and associate JS settings.
-          $id = Html::getClass($name);
-          $row['#attributes']['id'] = $id;
-          if (isset($row['#js_settings'])) {
-            $row['#js_settings'] += array(
-              'rowHandler' => $row['#row_type'],
-              'name' => $name,
-              'region' => $region_name,
-            );
-            $js_settings[$id] = $row['#js_settings'];
-          }
-        }
-      }
-    }
-    // Determine rendering order from the tree structure.
-    foreach ($regions as $region_name => $region) {
-      $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array($this, 'reduceOrder'));
-    }
-
-    $elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings;
-
-    // If the custom #tabledrag is set and there is a HTML ID, add the table's
-    // HTML ID to the options and attach the behavior.
-    // @see \Drupal\Core\Render\Element\Table::preRenderTable()
-    if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
-      foreach ($elements['#tabledrag'] as $options) {
-        $options['table_id'] = $elements['#attributes']['id'];
-        drupal_attach_tabledrag($elements, $options);
-      }
-    }
-
-    return $elements;
-  }
-
-  /**
-   * Determines the rendering order of an array representing a tree.
-   *
-   * Callback for array_reduce() within
-   * \Drupal\field_ui\DisplayOverviewBase::tablePreRender().
-   */
-  public function reduceOrder($array, $a) {
-    $array = !isset($array) ? array() : $array;
-    if ($a['name']) {
-      $array[] = $a['name'];
-    }
-    if (!empty($a['children'])) {
-      uasort($a['children'], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
-      $array = array_merge($array, array_reduce($a['children'], array($this, 'reduceOrder')));
-    }
-    return $array;
-  }
-
-  /**
    * Returns the entity display object used by this form.
    *
    * @param string $mode
diff --git a/core/modules/field_ui/src/FieldConfigListBuilder.php b/core/modules/field_ui/src/FieldConfigListBuilder.php
index ea5170f..cae4250 100644
--- a/core/modules/field_ui/src/FieldConfigListBuilder.php
+++ b/core/modules/field_ui/src/FieldConfigListBuilder.php
@@ -7,15 +7,10 @@
 
 namespace Drupal\field_ui;
 
-use Drupal\Component\Utility\Html;
-use Drupal\Component\Utility\String;
 use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Field\FieldTypePluginManagerInterface;
-use Drupal\Core\Url;
-use Drupal\field\FieldConfigInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -24,20 +19,6 @@
 class FieldConfigListBuilder extends ConfigEntityListBuilder {
 
   /**
-   * The name of the entity type the listed fields are attached to.
-   *
-   * @var string
-   */
-  protected $targetEntityTypeId;
-
-  /**
-   * The name of the bundle the listed fields are attached to.
-   *
-   * @var string
-   */
-  protected $targetBundle;
-
-  /**
    * The entity manager.
    *
    * @var \Drupal\Core\Entity\EntityManagerInterface
@@ -45,116 +26,33 @@ class FieldConfigListBuilder extends ConfigEntityListBuilder {
   protected $entityManager;
 
   /**
-   * The field type plugin manager.
-   *
-   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
-   */
-  protected $fieldTypeManager;
-
-  /**
    * Constructs a new class instance.
    *
    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
    *   The entity type definition.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
-   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
-   *   The field type manager
    */
-  public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
+  public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager) {
     parent::__construct($entity_type, $entity_manager->getStorage($entity_type->id()));
-
     $this->entityManager = $entity_manager;
-    $this->fieldTypeManager = $field_type_manager;
   }
 
   /**
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
-    return new static($entity_type, $container->get('entity.manager'), $container->get('plugin.manager.field.field_type'));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function render($target_entity_type_id = NULL, $target_bundle = NULL) {
-    $this->targetEntityTypeId = $target_entity_type_id;
-    $this->targetBundle = $target_bundle;
-
-    $build = parent::render();
-    $build['#attributes']['id'] = 'field-overview';
-    $build['#empty'] = $this->t('No fields are present yet.');
-
-    return $build;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function load() {
-    $entities = array_filter($this->entityManager->getFieldDefinitions($this->targetEntityTypeId, $this->targetBundle), function ($field_definition) {
-      return $field_definition instanceof FieldConfigInterface;
-    });
-
-    // Sort the entities using the entity class's sort() method.
-    // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
-    uasort($entities, array($this->entityType->getClass(), 'sort'));
-    return $entities;
+    return new static($entity_type, $container->get('entity.manager'));
   }
 
   /**
    * {@inheritdoc}
    */
-  public function buildHeader() {
-    $header = array(
-      'label' => $this->t('Label'),
-      'field_name' => array(
-        'data' => $this->t('Machine name'),
-        'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
-      ),
-      'field_type' => $this->t('Field type'),
-    );
-    return $header + parent::buildHeader();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildRow(EntityInterface $field_config) {
-    /** @var \Drupal\field\FieldConfigInterface $field_config */
-    $field_storage = $field_config->getFieldStorageDefinition();
-    $target_bundle_entity_type_id = $this->entityManager->getDefinition($this->targetEntityTypeId)->getBundleEntityType();
-    $route_parameters = array(
-      $target_bundle_entity_type_id => $this->targetBundle,
-      'field_config' => $field_config->id(),
-    );
-
-    $row = array(
-      'id' => Html::getClass($field_config->getName()),
-      'data' => array(
-        'label' => String::checkPlain($field_config->getLabel()),
-        'field_name' => $field_config->getName(),
-        'field_type' => array(
-          'data' => array(
-            '#type' => 'link',
-            '#title' => $this->fieldTypeManager->getDefinitions()[$field_storage->getType()]['label'],
-            '#url' => Url::fromRoute('field_ui.storage_edit_' . $this->targetEntityTypeId, $route_parameters),
-            '#options' => array('attributes' => array('title' => $this->t('Edit field settings.'))),
-          ),
-        ),
-      ),
-    );
-
-    // Add the operations.
-    $row['data'] = $row['data'] + parent::buildRow($field_config);
-
-    if (!empty($field_storage->locked)) {
-      $row['data']['operations'] = array('data' => array('#markup' => $this->t('Locked')));
-      $row['class'][] = 'menu-disabled';
-    }
-
-    return $row;
+  public function render() {
+    // The actual field config overview is rendered by
+    // \Drupal\field_ui\FieldOverview, so we should not use this class to build
+    // lists.
+    throw new \Exception('This class is only used for operations and not for building lists.');
   }
 
   /**
diff --git a/core/modules/field_ui/src/FieldOverview.php b/core/modules/field_ui/src/FieldOverview.php
new file mode 100644
index 0000000..8e9d9ed
--- /dev/null
+++ b/core/modules/field_ui/src/FieldOverview.php
@@ -0,0 +1,537 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field_ui\FieldOverview.
+ */
+
+namespace Drupal\field_ui;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Entity\EntityListBuilderInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Url;
+use Drupal\field\FieldStorageConfigInterface;
+use Drupal\field_ui\OverviewBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\field\FieldConfigInterface;
+
+/**
+ * Field UI field overview form.
+ */
+class FieldOverview extends OverviewBase {
+
+  /**
+   *  The field type manager.
+   *
+   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
+   */
+  protected $fieldTypeManager;
+
+  /**
+   * Constructs a new FieldOverview.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
+   *   The field type manager
+   */
+  public function __construct(EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
+    parent::__construct($entity_manager);
+    $this->fieldTypeManager = $field_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('plugin.manager.field.field_type')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRegions() {
+    return array(
+      'content' => array(
+        'title' => $this->t('Content'),
+        'invisible' => TRUE,
+        // @todo Bring back this message in https://drupal.org/node/1963340.
+        //'message' => $this->t('No fields are present yet.'),
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'field_ui_field_overview_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL) {
+    parent::buildForm($form, $form_state, $entity_type_id, $bundle);
+
+    // Gather bundle information.
+    $fields = array_filter(\Drupal::entityManager()->getFieldDefinitions($this->entity_type, $this->bundle), function ($field_definition) {
+      return $field_definition instanceof FieldConfigInterface;
+    });
+    $field_types = $this->fieldTypeManager->getDefinitions();
+
+    // Field prefix.
+    $field_prefix = \Drupal::config('field_ui.settings')->get('field_prefix');
+
+    $form += array(
+      '#entity_type' => $this->entity_type,
+      '#bundle' => $this->bundle,
+      '#fields' => array_keys($fields),
+    );
+
+    $table = array(
+      '#type' => 'field_ui_table',
+      '#tree' => TRUE,
+      '#header' => array(
+        $this->t('Label'),
+        array(
+          'data' => $this->t('Machine name'),
+          'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
+        ),
+        $this->t('Field type'),
+        $this->t('Operations'),
+      ),
+      '#regions' => $this->getRegions(),
+      '#attributes' => array(
+        'class' => array('field-ui-overview'),
+        'id' => 'field-overview',
+      ),
+    );
+
+    // Fields.
+    foreach ($fields as $name => $field) {
+      $field_storage = $field->getFieldStorageDefinition();
+      $route_parameters = array(
+        $this->bundleEntityType => $this->bundle,
+        'field_config' => $field->id(),
+      );
+      $table[$name] = array(
+        '#attributes' => array(
+          'id' => Html::getClass($name),
+        ),
+        'label' => array(
+          '#markup' => String::checkPlain($field->getLabel()),
+        ),
+        'field_name' => array(
+          '#markup' => $field->getName(),
+        ),
+        'type' => array(
+          '#type' => 'link',
+          '#title' => $field_types[$field_storage->getType()]['label'],
+          '#url' => Url::fromRoute('field_ui.storage_edit_' . $this->entity_type, $route_parameters),
+          '#options' => array('attributes' => array('title' => $this->t('Edit field settings.'))),
+        ),
+      );
+
+      $table[$name]['operations']['data'] = array(
+        '#type' => 'operations',
+        '#links' => $this->entityManager->getListBuilder('field_config')->getOperations($field),
+      );
+
+      if (!empty($field_storage->locked)) {
+        $table[$name]['operations'] = array('#markup' => $this->t('Locked'));
+        $table[$name]['#attributes']['class'][] = 'menu-disabled';
+      }
+    }
+
+    // Gather valid field types.
+    $field_type_options = array();
+    foreach ($field_types as $name => $field_type) {
+      // Skip field types which should not be added via user interface.
+      if (empty($field_type['no_ui'])) {
+        $field_type_options[$name] = $field_type['label'];
+      }
+    }
+    asort($field_type_options);
+
+    // Additional row: add new field.
+    if ($field_type_options) {
+      $name = '_add_new_field';
+      $table[$name] = array(
+        '#attributes' => array('class' => array('add-new')),
+        'label' => array(
+          '#type' => 'textfield',
+          '#title' => $this->t('New field label'),
+          '#title_display' => 'invisible',
+          '#size' => 15,
+          '#description' => $this->t('Label'),
+          '#prefix' => '<div class="label-input"><div class="add-new-placeholder">' . $this->t('Add new field') .'</div>',
+          '#suffix' => '</div>',
+        ),
+        'field_name' => array(
+          '#type' => 'machine_name',
+          '#title' => $this->t('New field name'),
+          '#title_display' => 'invisible',
+          // This field should stay LTR even for RTL languages.
+          '#field_prefix' => '<span dir="ltr">' . $field_prefix,
+          '#field_suffix' => '</span>&lrm;',
+          '#size' => 15,
+          '#description' => $this->t('A unique machine-readable name containing letters, numbers, and underscores.'),
+          // Calculate characters depending on the length of the field prefix
+          // setting. Maximum length is 32.
+          '#maxlength' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix),
+          '#prefix' => '<div class="add-new-placeholder">&nbsp;</div>',
+          '#machine_name' => array(
+            'source' => array('fields', $name, 'label'),
+            'exists' => array($this, 'fieldNameExists'),
+            'standalone' => TRUE,
+            'label' => '',
+          ),
+          '#required' => FALSE,
+        ),
+        'type' => array(
+          '#type' => 'select',
+          '#title' => $this->t('Type of new field'),
+          '#title_display' => 'invisible',
+          '#options' => $field_type_options,
+          '#empty_option' => $this->t('- Select a field type -'),
+          '#description' => $this->t('Type of data to store.'),
+          '#attributes' => array('class' => array('field-type-select')),
+          '#cell_attributes' => array('colspan' => 2),
+          '#prefix' => '<div class="add-new-placeholder">&nbsp;</div>',
+        ),
+        // Place the 'translatable' property as an explicit value so that
+        // contrib modules can form_alter() the value for newly created fields.
+        'translatable' => array(
+          '#type' => 'value',
+          '#value' => TRUE,
+        ),
+      );
+    }
+
+    // Additional row: re-use existing field storages.
+    $existing_fields = $this->getExistingFieldStorageOptions();
+    if ($existing_fields) {
+      // Build list of options.
+      $existing_field_options = array();
+      foreach ($existing_fields as $field_name => $info) {
+        $text = $this->t('@type: @field', array(
+          '@type' => $info['type_label'],
+          '@field' => $info['field'],
+        ));
+        $existing_field_options[$field_name] = Unicode::truncate($text, 80, FALSE, TRUE);
+      }
+      asort($existing_field_options);
+      $name = '_add_existing_field';
+      $table[$name] = array(
+        '#attributes' => array('class' => array('add-new')),
+        '#row_type' => 'add_new_field',
+        '#region_callback' => array($this, 'getRowRegion'),
+        'label' => array(
+          '#type' => 'textfield',
+          '#title' => $this->t('Existing field label'),
+          '#title_display' => 'invisible',
+          '#size' => 15,
+          '#description' => $this->t('Label'),
+          '#attributes' => array('class' => array('label-textfield')),
+          '#prefix' => '<div class="label-input"><div class="add-new-placeholder">' . $this->t('Re-use existing field') .'</div>',
+          '#suffix' => '</div>',
+        ),
+        'field_name' => array(
+          '#type' => 'select',
+          '#title' => $this->t('Existing field to share'),
+          '#title_display' => 'invisible',
+          '#options' => $existing_field_options,
+          '#empty_option' => $this->t('- Select an existing field -'),
+          '#description' => $this->t('Field to share'),
+          '#attributes' => array('class' => array('field-select')),
+          '#cell_attributes' => array('colspan' => 3),
+          '#prefix' => '<div class="add-new-placeholder">&nbsp;</div>',
+        ),
+      );
+    }
+
+    // We can set the 'rows_order' element, needed by theme_field_ui_table(),
+    // here instead of a #pre_render callback because this form doesn't have the
+    // tabledrag behavior anymore.
+    $table['#regions']['content']['rows_order'] = array();
+    foreach (Element::children($table) as $name) {
+      $table['#regions']['content']['rows_order'][] = $name;
+    }
+
+    $form['fields'] = $table;
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#button_type' => 'primary',
+      '#value' => $this->t('Save'));
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    $this->validateAddNew($form, $form_state);
+    $this->validateAddExisting($form, $form_state);
+  }
+
+  /**
+   * Validates the 'add new field' row.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @see \Drupal\field_ui\FieldOverview::validateForm()
+   */
+  protected function validateAddNew(array $form, FormStateInterface $form_state) {
+    $field = $form_state->getValue(array('fields', '_add_new_field'));
+
+    // Validate if any information was provided in the 'add new field' row.
+    if (array_filter(array($field['label'], $field['field_name'], $field['type']))) {
+      // Missing label.
+      if (!$field['label']) {
+        $form_state->setErrorByName('fields][_add_new_field][label', $this->t('Add new field: you need to provide a label.'));
+      }
+
+      // Missing field name.
+      if (!$field['field_name']) {
+        $form_state->setErrorByName('fields][_add_new_field][field_name', $this->t('Add new field: you need to provide a machine name for the field.'));
+      }
+      // Field name validation.
+      else {
+        $field_name = $field['field_name'];
+
+        // Add the field prefix.
+        $field_name = \Drupal::config('field_ui.settings')->get('field_prefix') . $field_name;
+        $form_state->setValueForElement($form['fields']['_add_new_field']['field_name'], $field_name);
+      }
+
+      // Missing field type.
+      if (!$field['type']) {
+        $form_state->setErrorByName('fields][_add_new_field][type', $this->t('Add new field: you need to select a field type.'));
+      }
+    }
+  }
+
+  /**
+   * Validates the 're-use existing field' row.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @see \Drupal\field_ui\FieldOverview::validate()
+   */
+  protected function validateAddExisting(array $form, FormStateInterface $form_state) {
+    // The form element might be absent if no existing fields can be added to
+    // this bundle.
+    if ($field = $form_state->getValue(array('fields', '_add_existing_field'))) {
+      // Validate if any information was provided in the
+      // 're-use existing field' row.
+      if (array_filter(array($field['label'], $field['field_name']))) {
+        // Missing label.
+        if (!$field['label']) {
+          $form_state->setErrorByName('fields][_add_existing_field][label', $this->t('Re-use existing field: you need to provide a label.'));
+        }
+
+        // Missing existing field name.
+        if (!$field['field_name']) {
+          $form_state->setErrorByName('fields][_add_existing_field][field_name', $this->t('Re-use existing field: you need to select a field.'));
+        }
+      }
+    }
+  }
+
+  /**
+   * Overrides \Drupal\field_ui\OverviewBase::submitForm().
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $error = FALSE;
+    $form_values = $form_state->getValue('fields');
+    $destinations = array();
+
+    // Create new field.
+    if (!empty($form_values['_add_new_field']['field_name'])) {
+      $values = $form_values['_add_new_field'];
+
+      $field_storage = array(
+        'field_name' => $values['field_name'],
+        'entity_type' => $this->entity_type,
+        'type' => $values['type'],
+        'translatable' => $values['translatable'],
+      );
+      $field = array(
+        'field_name' => $values['field_name'],
+        'entity_type' => $this->entity_type,
+        'bundle' => $this->bundle,
+        'label' => $values['label'],
+        // Field translatability should be explicitly enabled by the users.
+        'translatable' => FALSE,
+      );
+
+      // Create the field storage and field.
+      try {
+        $this->entityManager->getStorage('field_storage_config')->create($field_storage)->save();
+        $new_field = $this->entityManager->getStorage('field_config')->create($field);
+        $new_field->save();
+
+        // Make sure the field is displayed in the 'default' form mode (using
+        // default widget and settings). It stays hidden for other form modes
+        // until it is explicitly configured.
+        entity_get_form_display($this->entity_type, $this->bundle, 'default')
+          ->setComponent($values['field_name'])
+          ->save();
+
+        // Make sure the field is displayed in the 'default' view mode (using
+        // default formatter and settings). It stays hidden for other view
+        // modes until it is explicitly configured.
+        entity_get_display($this->entity_type, $this->bundle, 'default')
+          ->setComponent($values['field_name'])
+          ->save();
+
+        // Always show the field settings step, as the cardinality needs to be
+        // configured for new fields.
+        $route_parameters = array(
+          $this->bundleEntityType => $this->bundle,
+          'field_config' => $new_field->id(),
+        );
+        $destinations[] = array('route_name' => 'field_ui.storage_edit_' . $this->entity_type, 'route_parameters' => $route_parameters);
+        $destinations[] = array('route_name' => 'field_ui.field_edit_' . $this->entity_type, 'route_parameters' => $route_parameters);
+
+        // Store new field information for any additional submit handlers.
+        $form_state->set(['fields_added', '_add_new_field'], $values['field_name']);
+      }
+      catch (\Exception $e) {
+        $error = TRUE;
+        drupal_set_message($this->t('There was a problem creating field %label: !message', array('%label' => $field['label'], '!message' => $e->getMessage())), 'error');
+      }
+    }
+
+    // Re-use existing field.
+    if (!empty($form_values['_add_existing_field']['field_name'])) {
+      $values = $form_values['_add_existing_field'];
+      $field_name = $values['field_name'];
+      $field_storage = FieldStorageConfig::loadByName($this->entity_type, $field_name);
+      if (!empty($field_storage->locked)) {
+        drupal_set_message($this->t('The field %label cannot be added because it is locked.', array('%label' => $values['label'])), 'error');
+      }
+      else {
+        $field = array(
+          'field_name' => $field_name,
+          'entity_type' => $this->entity_type,
+          'bundle' => $this->bundle,
+          'label' => $values['label'],
+        );
+
+        try {
+          $new_field = $this->entityManager->getStorage('field_config')->create($field);
+          $new_field->save();
+
+          // Make sure the field is displayed in the 'default' form mode (using
+          // default widget and settings). It stays hidden for other form modes
+          // until it is explicitly configured.
+          entity_get_form_display($this->entity_type, $this->bundle, 'default')
+            ->setComponent($field_name)
+            ->save();
+
+          // Make sure the field is displayed in the 'default' view mode (using
+          // default formatter and settings). It stays hidden for other view
+          // modes until it is explicitly configured.
+          entity_get_display($this->entity_type, $this->bundle, 'default')
+            ->setComponent($field_name)
+            ->save();
+
+          $destinations[] = array(
+            'route_name' => 'field_ui.field_edit_' . $this->entity_type,
+            'route_parameters' => array(
+              $this->bundleEntityType => $this->bundle,
+              'field_config' => $new_field->id(),
+            ),
+          );
+          // Store new field information for any additional submit handlers.
+          $form_state->set(['fields_added', '_add_existing_field'], $field['field_name']);
+        }
+        catch (\Exception $e) {
+          $error = TRUE;
+          drupal_set_message($this->t('There was a problem creating field %label: @message.', array('%label' => $field['label'], '@message' => $e->getMessage())), 'error');
+        }
+      }
+    }
+
+    if ($destinations) {
+      $destination = drupal_get_destination();
+      $destinations[] = $destination['destination'];
+      $form_state->setRedirectUrl(FieldUI::getNextDestination($destinations, $form_state));
+    }
+    elseif (!$error) {
+      drupal_set_message($this->t('Your settings have been saved.'));
+    }
+  }
+
+  /**
+   * Returns an array of existing field storages that can be added to a bundle.
+   *
+   * @return array
+   *   An array of existing field storages keyed by name.
+   */
+  protected function getExistingFieldStorageOptions() {
+    $options = array();
+    // Load the field_storages and build the list of options.
+    $field_types = $this->fieldTypeManager->getDefinitions();
+    foreach ($this->entityManager->getFieldStorageDefinitions($this->entity_type) as $field_name => $field_storage) {
+      // Do not show:
+      // - non-configurable field storages,
+      // - locked field_storages,
+      // - field_storages that should not be added via user interface,
+      // - field_storages that already have a field in the bundle.
+      $field_type = $field_storage->getType();
+      if ($field_storage instanceof FieldStorageConfigInterface
+        && !$field_storage->isLocked()
+        && empty($field_types[$field_type]['no_ui'])
+        && !in_array($this->bundle, $field_storage->getBundles(), TRUE)) {
+        $options[$field_name] = array(
+          'type' => $field_type,
+          'type_label' => $field_types[$field_type]['label'],
+          'field' => $field_name,
+        );
+      }
+    }
+
+    return $options;
+  }
+
+  /**
+   * Checks if a field machine name is taken.
+   *
+   * @param string $value
+   *   The machine name, not prefixed.
+   *
+   * @return bool
+   *   Whether or not the field machine name is taken.
+   */
+  public function fieldNameExists($value) {
+    // Add the field prefix.
+    $field_name = \Drupal::config('field_ui.settings')->get('field_prefix') . $value;
+
+    $field_storage_definitions = \Drupal::entityManager()->getFieldStorageDefinitions($this->entity_type);
+    return isset($field_storage_definitions[$field_name]);
+  }
+
+}
diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
deleted file mode 100644
index a9fb7f6..0000000
--- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php
+++ /dev/null
@@ -1,524 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\field_ui\Form\FieldStorageAddForm.
- */
-
-namespace Drupal\field_ui\Form;
-
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\Entity\Query\QueryFactory;
-use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Field\FieldTypePluginManagerInterface;
-use Drupal\Core\Form\FormBase;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\field\FieldStorageConfigInterface;
-use Drupal\field_ui\FieldUI;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides a form for the "field storage" add page.
- */
-class FieldStorageAddForm extends FormBase {
-
-  /**
-   * The name of the entity type.
-   *
-   * @var string
-   */
-  protected $entityTypeId;
-
-  /**
-   * The entity bundle.
-   *
-   * @var string
-   */
-  protected $bundle;
-
-  /**
-   * The name of the entity type which provides bundles for the entity type
-   * defined above.
-   *
-   * @var string
-   */
-  protected $bundleEntityTypeId;
-
-  /**
-   * The entity manager.
-   *
-   * @var \Drupal\Core\Entity\EntityManager
-   */
-  protected $entityManager;
-
-  /**
-   * The field type plugin manager.
-   *
-   * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
-   */
-  protected $fieldTypePluginManager;
-
-  /**
-   * The query factory to create entity queries.
-   *
-   * @var \Drupal\Core\Entity\Query\QueryFactoryInterface
-   */
-  public $queryFactory;
-
-  /**
-   * The configuration factory.
-   *
-   * @var \Drupal\Core\Config\ConfigFactoryInterface
-   */
-  protected $configFactory;
-
-  /**
-   * Constructs a new FieldStorageAddForm object.
-   *
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
-   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager
-   *   The field type plugin manager.
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The configuration factory.
-   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
-   *   The entity query factory.
-   */
-  public function __construct(EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, QueryFactory $query_factory, ConfigFactoryInterface $config_factory) {
-    $this->entityManager = $entity_manager;
-    $this->fieldTypePluginManager = $field_type_plugin_manager;
-    $this->queryFactory = $query_factory;
-    $this->configFactory = $config_factory;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormID() {
-    return 'field_ui_field_storage_add_form';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('entity.manager'),
-      $container->get('plugin.manager.field.field_type'),
-      $container->get('entity.query'),
-      $container->get('config.factory')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL) {
-    $entity_type = $this->entityManager->getDefinition($entity_type_id);
-    $this->bundleEntityTypeId = $entity_type->getBundleEntityType();
-
-    if (!$form_state->get('entity_type_id')) {
-      $form_state->set('entity_type_id', $entity_type_id);
-    }
-    if (!$form_state->get('bundle')) {
-      $bundle = $bundle ?: $this->getRequest()->attributes->get('_raw_variables')->get($this->bundleEntityTypeId);
-      $form_state->set('bundle', $bundle);
-    }
-
-    $this->entityTypeId = $form_state->get('entity_type_id');
-    $this->bundle = $form_state->get('bundle');
-
-    // Gather valid field types.
-    $field_type_options = array();
-    foreach ($this->fieldTypePluginManager->getDefinitions() as $name => $field_type) {
-      // Skip field types which should not be added via user interface.
-      if (empty($field_type['no_ui'])) {
-        $field_type_options[$name] = $field_type['label'];
-      }
-    }
-    asort($field_type_options);
-
-    $form['add'] = array(
-      '#type' => 'container',
-      '#attributes' => array('class' => array('field-type-wrapper', 'clearfix')),
-    );
-
-    $form['add']['new_storage_type'] = array(
-      '#type' => 'select',
-      '#title' => $this->t('Add a new field'),
-      '#options' => $field_type_options,
-      '#empty_option' => $this->t('- Select a field type -'),
-    );
-
-    // Re-use existing field.
-    if ($existing_field_storage_options = $this->getExistingFieldStorageOptions()) {
-      $form['add']['separator'] = array(
-        '#type' => 'item',
-        '#markup' => $this->t('or'),
-      );
-      $form['add']['existing_storage_name'] = array(
-        '#type' => 'select',
-        '#title' => $this->t('Re-use an existing field'),
-        '#options' => $existing_field_storage_options,
-        '#empty_option' => $this->t('- Select an existing field -'),
-      );
-
-      $form['#attached']['drupalSettings']['existingFieldLabels'] = $this->getExistingFieldLabels(array_keys($existing_field_storage_options));
-    }
-    else {
-      // Provide a placeholder form element to simplify the validation code.
-      $form['add']['existing_storage_name'] = array(
-        '#type' => 'value',
-        '#value' => FALSE,
-      );
-    }
-
-    // Field label and field_name.
-    $form['new_storage_wrapper'] = array(
-      '#type' => 'container',
-      '#states' => array(
-        '!visible' => array(
-          ':input[name="new_storage_type"]' => array('value' => ''),
-        ),
-      ),
-    );
-    $form['new_storage_wrapper']['label'] = array(
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#size' => 15,
-    );
-
-    $field_prefix = $this->config('field_ui.settings')->get('field_prefix');
-    $form['new_storage_wrapper']['field_name'] = array(
-      '#type' => 'machine_name',
-      // This field should stay LTR even for RTL languages.
-      '#field_prefix' => '<span dir="ltr">' . $field_prefix,
-      '#field_suffix' => '</span>&lrm;',
-      '#size' => 15,
-      '#description' => $this->t('A unique machine-readable name containing letters, numbers, and underscores.'),
-      // Calculate characters depending on the length of the field prefix
-      // setting. Maximum length is 32.
-      '#maxlength' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix),
-      '#machine_name' => array(
-        'source' => array('new_storage_wrapper', 'label'),
-        'exists' => array($this, 'fieldNameExists'),
-      ),
-      '#required' => FALSE,
-    );
-
-    // Provide a separate label element for the "Re-use existing field" case
-    // and place it outside the $form['add'] wrapper because those elements
-    // are displayed inline.
-    if ($existing_field_storage_options) {
-      $form['existing_storage_label'] = array(
-        '#type' => 'textfield',
-        '#title' => $this->t('Label'),
-        '#size' => 15,
-        '#states' => array(
-          '!visible' => array(
-            ':input[name="existing_storage_name"]' => array('value' => ''),
-          ),
-        ),
-      );
-    }
-
-    // Place the 'translatable' property as an explicit value so that
-    // contrib modules can form_alter() the value for newly created fields.
-    $form['translatable'] = array(
-      '#type' => 'value',
-      '#value' => FALSE,
-    );
-
-    $form['actions'] = array('#type' => 'actions');
-    $form['actions']['submit'] = array(
-      '#type' => 'submit',
-      '#value' => $this->t('Save and continue'),
-      '#button_type' => 'primary',
-    );
-
-    $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    // Missing field type.
-    if (!$form_state->getValue('new_storage_type') && !$form_state->getValue('existing_storage_name')) {
-      $form_state->setErrorByName('new_storage_type', $this->t('You need to select a field type or an existing field.'));
-    }
-    // Both field type and existing field option selected. This is prevented in
-    // the UI with JavaScript but we also need a proper server-side validation.
-    elseif ($form_state->getValue('new_storage_type') && $form_state->getValue('existing_storage_name')) {
-      $form_state->setErrorByName('new_storage_type', $this->t('Adding a new field and re-using an existing field at the same time is not allowed.'));
-      return;
-    }
-
-    $this->validateAddNew($form, $form_state);
-    $this->validateAddExisting($form, $form_state);
-  }
-
-  /**
-   * Validates the 'add new field' case.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @see \Drupal\field_ui\Form\FieldStorageAddForm::validateForm()
-   */
-  protected function validateAddNew(array $form, FormStateInterface $form_state) {
-    // Validate if any information was provided in the 'add new field' case.
-    if ($form_state->getValue('new_storage_type')) {
-      // Missing label.
-      if (!$form_state->getValue('label')) {
-        $form_state->setErrorByName('label', $this->t('Add new field: you need to provide a label.'));
-      }
-
-      // Missing field name.
-      if (!$form_state->getValue('field_name')) {
-        $form_state->setErrorByName('field_name', $this->t('Add new field: you need to provide a machine name for the field.'));
-      }
-      // Field name validation.
-      else {
-        $field_name = $form_state->getValue('field_name');
-
-        // Add the field prefix.
-        $field_name = $this->configFactory->get('field_ui.settings')->get('field_prefix') . $field_name;
-        $form_state->setValueForElement($form['new_storage_wrapper']['field_name'], $field_name);
-      }
-    }
-  }
-
-  /**
-   * Validates the 're-use existing field' case.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @see \Drupal\field_ui\Form\FieldStorageAddForm::validateForm()
-   */
-  protected function validateAddExisting(array $form, FormStateInterface $form_state) {
-    if ($form_state->getValue('existing_storage_name')) {
-      // Missing label.
-      if (!$form_state->getValue('existing_storage_label')) {
-        $form_state->setErrorByName('existing_storage_label', $this->t('Re-use existing field: you need to provide a label.'));
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    $error = FALSE;
-    $values = $form_state->getValues();
-    $destinations = array();
-
-    // Create new field.
-    if ($values['new_storage_type']) {
-      // Create the field storage and field.
-      try {
-        $this->entityManager->getStorage('field_storage_config')->create(array(
-          'field_name' => $values['field_name'],
-          'entity_type' => $this->entityTypeId,
-          'type' => $values['new_storage_type'],
-          'translatable' => $values['translatable'],
-        ))->save();
-
-        $field = $this->entityManager->getStorage('field_config')->create(array(
-          'field_name' => $values['field_name'],
-          'entity_type' => $this->entityTypeId,
-          'bundle' => $this->bundle,
-          'label' => $values['label'],
-          // Field translatability should be explicitly enabled by the users.
-          'translatable' => FALSE,
-        ));
-        $field->save();
-
-        $this->configureEntityDisplays($values['field_name']);
-
-        // Always show the field settings step, as the cardinality needs to be
-        // configured for new fields.
-        $route_parameters = array(
-          $this->bundleEntityTypeId => $this->bundle,
-          'field_config' => $field->id(),
-        );
-        $destinations[] = array('route_name' => 'field_ui.storage_edit_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
-        $destinations[] = array('route_name' => 'field_ui.field_edit_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
-        $destinations[] = array('route_name' => 'field_ui.overview_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
-
-        // Store new field information for any additional submit handlers.
-        $form_state->set(['fields_added', '_add_new_field'], $values['field_name']);
-      }
-      catch (\Exception $e) {
-        $error = TRUE;
-        drupal_set_message($this->t('There was a problem creating field %label: !message', array('%label' => $values['label'], '!message' => $e->getMessage())), 'error');
-      }
-    }
-
-    // Re-use existing field.
-    if ($values['existing_storage_name']) {
-      $field_name = $values['existing_storage_name'];
-
-      try {
-        $field = $this->entityManager->getStorage('field_config')->create(array(
-          'field_name' => $field_name,
-          'entity_type' => $this->entityTypeId,
-          'bundle' => $this->bundle,
-          'label' => $values['existing_storage_label'],
-        ));
-        $field->save();
-
-        $this->configureEntityDisplays($field_name);
-
-        $route_parameters = array(
-          $this->bundleEntityTypeId => $this->bundle,
-          'field_config' => $field->id(),
-        );
-        $destinations[] = array('route_name' => 'field_ui.field_edit_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
-        $destinations[] = array('route_name' => 'field_ui.overview_' . $this->entityTypeId, 'route_parameters' => $route_parameters);
-
-        // Store new field information for any additional submit handlers.
-        $form_state->set(['fields_added', '_add_existing_field'], $field_name);
-      }
-      catch (\Exception $e) {
-        $error = TRUE;
-        drupal_set_message($this->t('There was a problem creating field %label: !message', array('%label' => $values['label'], '!message' => $e->getMessage())), 'error');
-      }
-    }
-
-    if ($destinations) {
-      $destination = drupal_get_destination();
-      $destinations[] = $destination['destination'];
-      $form_state->setRedirectUrl(FieldUI::getNextDestination($destinations, $form_state));
-    }
-    elseif (!$error) {
-      drupal_set_message($this->t('Your settings have been saved.'));
-    }
-  }
-
-  /**
-   * Configures the newly created field for the default view and form modes.
-   *
-   * @param string $field_name
-   *   The field name.
-   */
-  protected function configureEntityDisplays($field_name) {
-    // Make sure the field is displayed in the 'default' form mode (using
-    // default widget and settings). It stays hidden for other form modes
-    // until it is explicitly configured.
-    entity_get_form_display($this->entityTypeId, $this->bundle, 'default')
-      ->setComponent($field_name)
-      ->save();
-
-    // Make sure the field is displayed in the 'default' view mode (using
-    // default formatter and settings). It stays hidden for other view
-    // modes until it is explicitly configured.
-    entity_get_display($this->entityTypeId, $this->bundle, 'default')
-      ->setComponent($field_name)
-      ->save();
-  }
-
-  /**
-   * Returns an array of existing field storages that can be added to a bundle.
-   *
-   * @return array
-   *   An array of existing field storages keyed by name.
-   */
-  protected function getExistingFieldStorageOptions() {
-    $options = array();
-    // Load the field_storages and build the list of options.
-    $field_types = $this->fieldTypePluginManager->getDefinitions();
-    foreach ($this->entityManager->getFieldStorageDefinitions($this->entityTypeId) as $field_name => $field_storage) {
-      // Do not show:
-      // - non-configurable field storages,
-      // - locked field storages,
-      // - field storages that should not be added via user interface,
-      // - field storages that already have a field in the bundle.
-      $field_type = $field_storage->getType();
-      if ($field_storage instanceof FieldStorageConfigInterface
-        && !$field_storage->isLocked()
-        && empty($field_types[$field_type]['no_ui'])
-        && !in_array($this->bundle, $field_storage->getBundles(), TRUE)) {
-        $options[$field_name] = $this->t('@type: @field', array(
-          '@type' => $field_types[$field_type]['label'],
-          '@field' => $field_name,
-        ));
-      }
-    }
-    asort($options);
-
-    return $options;
-  }
-
-  /**
-   * Gets the human-readable labels for the given field storage names.
-   *
-   * Since not all field storages are required to have a field, we can only
-   * provide the field labels on a best-effort basis (e.g. the label of a field
-   * storage without any field attached to a bundle will be the field name).
-   *
-   * @param array $field_names
-   *   An array of field names.
-   *
-   * @return array
-   *   An array of field labels keyed by field name.
-   */
-  protected function getExistingFieldLabels(array $field_names) {
-    // Get all the fields corresponding to the given field storage names and
-    // this entity type.
-    $field_ids = $this->queryFactory->get('field_config')
-      ->condition('entity_type', $this->entityTypeId)
-      ->condition('field_name', $field_names)
-      ->execute();
-    $fields = $this->entityManager->getStorage('field_config')->loadMultiple($field_ids);
-
-    // Go through all the fields and use the label of the first encounter.
-    $labels = array();
-    foreach ($fields as $field) {
-      if (!isset($labels[$field->getName()])) {
-        $labels[$field->getName()] = $field->label();
-      }
-    }
-
-    // For field storages without any fields attached to a bundle, the default
-    // label is the field name.
-    $labels += array_combine($field_names, $field_names);
-
-    return $labels;
-  }
-
-  /**
-   * Checks if a field machine name is taken.
-   *
-   * @param string $value
-   *   The machine name, not prefixed.
-   * @param array $element
-   *   An array containing the structure of the 'field_name' element.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @return bool
-   *   Whether or not the field machine name is taken.
-   */
-  public function fieldNameExists($value, $element, FormStateInterface $form_state) {
-    // Don't validate the case when an existing field has been selected.
-    if ($form_state->getValue('existing_storage_name')) {
-      return FALSE;
-    }
-
-    // Add the field prefix.
-    $field_name = $this->configFactory->get('field_ui.settings')->get('field_prefix') . $value;
-
-    $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
-    return isset($field_storage_definitions[$field_name]);
-  }
-
-}
diff --git a/core/modules/field_ui/src/FormDisplayOverview.php b/core/modules/field_ui/src/FormDisplayOverview.php
index 1728044..05db56b 100644
--- a/core/modules/field_ui/src/FormDisplayOverview.php
+++ b/core/modules/field_ui/src/FormDisplayOverview.php
@@ -169,7 +169,7 @@ protected function getTableHeader() {
    */
   protected function getOverviewRoute($mode) {
     return Url::fromRoute('field_ui.form_display_overview_form_mode_' . $this->entity_type, [
-      $this->bundleEntityTypeId => $this->bundle,
+      $this->bundleEntityType => $this->bundle,
       'form_mode_name' => $mode,
     ]);
   }
diff --git a/core/modules/field_ui/src/OverviewBase.php b/core/modules/field_ui/src/OverviewBase.php
new file mode 100644
index 0000000..f9ab9c5
--- /dev/null
+++ b/core/modules/field_ui/src/OverviewBase.php
@@ -0,0 +1,247 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field_ui\OverviewBase.
+ */
+
+namespace Drupal\field_ui;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Abstract base class for Field UI overview forms.
+ */
+abstract class OverviewBase extends FormBase {
+
+  /**
+   * The name of the entity type.
+   *
+   * @var string
+   */
+  protected $entity_type = '';
+
+  /**
+   * The entity bundle.
+   *
+   * @var string
+   */
+  protected $bundle = '';
+
+  /**
+   * The entity type of the entity bundle.
+   *
+   * @var string
+   */
+  protected $bundleEntityType;
+
+  /**
+   * The entity view or form mode.
+   *
+   * @var string
+   */
+  protected $mode = '';
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new OverviewBase.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL) {
+    $entity_type = $this->entityManager->getDefinition($entity_type_id);
+    $this->bundleEntityType = $entity_type->getBundleEntityType();
+    $stored_bundle = $form_state->get('bundle');
+    if (!$stored_bundle) {
+      if (!$bundle) {
+        $bundle = $this->getRequest()->attributes->get('_raw_variables')->get($this->bundleEntityType);
+      }
+      $stored_bundle = $bundle;
+      $form_state->set('bundle', $bundle);
+    }
+
+    $this->entity_type = $entity_type_id;
+    $this->bundle = $stored_bundle;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * Get the regions needed to create the overview form.
+   *
+   * @return array
+   *   Example usage:
+   *   @code
+   *     return array(
+   *       'content' => array(
+   *         // label for the region.
+   *         'title' => $this->t('Content'),
+   *         // Indicates if the region is visible in the UI.
+   *         'invisible' => TRUE,
+   *         // A message to indicate that there is nothing to be displayed in
+   *         // the region.
+   *         'message' => $this->t('No field is displayed.'),
+   *       ),
+   *     );
+   *   @endcode
+   */
+  abstract public function getRegions();
+
+  /**
+   * Returns an associative array of all regions.
+   */
+  public function getRegionOptions() {
+    $options = array();
+    foreach ($this->getRegions() as $region => $data) {
+      $options[$region] = $data['title'];
+    }
+    return $options;
+  }
+
+  /**
+   * Performs pre-render tasks on field_ui_table elements.
+   *
+   * This function is assigned as a #pre_render callback in
+   * field_ui_element_info().
+   *
+   * @param array $element
+   *   A structured array containing two sub-levels of elements. Properties
+   *   used:
+   *   - #tabledrag: The value is a list of $options arrays that are passed to
+   *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
+   *     $options array.
+   *
+   * @see drupal_render()
+   * @see \Drupal\Core\Render\Element\Table::preRenderTable()
+   */
+  public function tablePreRender($elements) {
+    $js_settings = array();
+
+    // For each region, build the tree structure from the weight and parenting
+    // data contained in the flat form structure, to determine row order and
+    // indentation.
+    $regions = $elements['#regions'];
+    $tree = array('' => array('name' => '', 'children' => array()));
+    $trees = array_fill_keys(array_keys($regions), $tree);
+
+    $parents = array();
+    $children = Element::children($elements);
+    $list = array_combine($children, $children);
+
+    // Iterate on rows until we can build a known tree path for all of them.
+    while ($list) {
+      foreach ($list as $name) {
+        $row = &$elements[$name];
+        $parent = $row['parent_wrapper']['parent']['#value'];
+        // Proceed if parent is known.
+        if (empty($parent) || isset($parents[$parent])) {
+          // Grab parent, and remove the row from the next iteration.
+          $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array();
+          unset($list[$name]);
+
+          // Determine the region for the row.
+          $region_name = call_user_func($row['#region_callback'], $row);
+
+          // Add the element in the tree.
+          $target = &$trees[$region_name][''];
+          foreach ($parents[$name] as $key) {
+            $target = &$target['children'][$key];
+          }
+          $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']);
+
+          // Add tabledrag indentation to the first row cell.
+          if ($depth = count($parents[$name])) {
+            $children = Element::children($row);
+            $cell = current($children);
+            $indentation = array(
+              '#theme' => 'indentation',
+              '#size' => $depth,
+            );
+            $row[$cell]['#prefix'] = drupal_render($indentation) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : '');
+          }
+
+          // Add row id and associate JS settings.
+          $id = Html::getClass($name);
+          $row['#attributes']['id'] = $id;
+          if (isset($row['#js_settings'])) {
+            $row['#js_settings'] += array(
+              'rowHandler' => $row['#row_type'],
+              'name' => $name,
+              'region' => $region_name,
+            );
+            $js_settings[$id] = $row['#js_settings'];
+          }
+        }
+      }
+    }
+    // Determine rendering order from the tree structure.
+    foreach ($regions as $region_name => $region) {
+      $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array($this, 'reduceOrder'));
+    }
+
+    $elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings;
+
+    // If the custom #tabledrag is set and there is a HTML ID, add the table's
+    // HTML ID to the options and attach the behavior.
+    // @see \Drupal\Core\Render\Element\Table::preRenderTable()
+    if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
+      foreach ($elements['#tabledrag'] as $options) {
+        $options['table_id'] = $elements['#attributes']['id'];
+        drupal_attach_tabledrag($elements, $options);
+      }
+    }
+
+    return $elements;
+  }
+
+  /**
+   * Determines the rendering order of an array representing a tree.
+   *
+   * Callback for array_reduce() within
+   * \Drupal\field_ui\OverviewBase::tablePreRender().
+   */
+  public function reduceOrder($array, $a) {
+    $array = !isset($array) ? array() : $array;
+    if ($a['name']) {
+      $array[] = $a['name'];
+    }
+    if (!empty($a['children'])) {
+      uasort($a['children'], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
+      $array = array_merge($array, array_reduce($a['children'], array($this, 'reduceOrder')));
+    }
+    return $array;
+  }
+
+}
diff --git a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
deleted file mode 100644
index 88b47e8..0000000
--- a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-/**
- * @fie
- * Contains \Drupal\field_ui\Plugin\Derivative\FieldUiLocalAction.
- */
-
-namespace Drupal\field_ui\Plugin\Derivative;
-
-use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
-use Drupal\Core\Routing\RouteProviderInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-
-/**
- * Provides local action definitions for all entity bundles.
- */
-class FieldUiLocalAction extends DeriverBase implements ContainerDeriverInterface {
-
-  use StringTranslationTrait;
-
-  /**
-   * The entity manager
-   *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
-   */
-  protected $entityManager;
-
-  /**
-   * Constructs a FieldUiLocalAction object.
-   *
-   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
-   *   The route provider to load routes by name.
-   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
-   *   The entity manager.
-   */
-  public function __construct(RouteProviderInterface $route_provider, EntityManagerInterface $entity_manager) {
-    $this->routeProvider = $route_provider;
-    $this->entityManager = $entity_manager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, $base_plugin_id) {
-    return new static(
-      $container->get('router.route_provider'),
-      $container->get('entity.manager')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getDerivativeDefinitions($base_plugin_definition) {
-    $this->derivatives = array();
-
-    foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
-      if ($entity_type->get('field_ui_base_route')) {
-        $this->derivatives["field_storage_config_add_$entity_type_id"] = array(
-          'route_name' => "field_ui.field_storage_config_add_$entity_type_id",
-          'title' => $this->t('Add field'),
-          'appears_on' => array("field_ui.overview_$entity_type_id"),
-        );
-      }
-    }
-
-    foreach ($this->derivatives as &$entry) {
-      $entry += $base_plugin_definition;
-    }
-
-    return $this->derivatives;
-  }
-
-}
diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php
index 5e9c265..3abb6cf 100644
--- a/core/modules/field_ui/src/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php
@@ -90,7 +90,7 @@ protected function alterRoutes(RouteCollection $collection) {
         $route = new Route(
           "$path/fields",
           array(
-            '_controller' => '\Drupal\field_ui\Controller\FieldConfigListController::listing',
+            '_form' => '\Drupal\field_ui\FieldOverview',
             '_title' => 'Manage fields',
           ) + $defaults,
           array('_permission' => 'administer ' . $entity_type_id . ' fields'),
@@ -99,16 +99,6 @@ protected function alterRoutes(RouteCollection $collection) {
         $collection->add("field_ui.overview_$entity_type_id", $route);
 
         $route = new Route(
-          "$path/fields/add-field",
-          array(
-            '_form' => '\Drupal\field_ui\Form\FieldStorageAddForm',
-            '_title' => 'Add field',
-          ) + $defaults,
-          array('_permission' => 'administer ' . $entity_type_id . ' fields')
-        );
-        $collection->add("field_ui.field_storage_config_add_$entity_type_id", $route);
-
-        $route = new Route(
           "$path/form-display",
           array(
             '_form' => '\Drupal\field_ui\FormDisplayOverview',
diff --git a/core/modules/field_ui/src/Tests/FieldUIRouteTest.php b/core/modules/field_ui/src/Tests/FieldUIRouteTest.php
index f88d7a2..1f4d3ea 100644
--- a/core/modules/field_ui/src/Tests/FieldUIRouteTest.php
+++ b/core/modules/field_ui/src/Tests/FieldUIRouteTest.php
@@ -19,7 +19,7 @@ class FieldUIRouteTest extends WebTestBase {
   /**
    * Modules to enable.
    */
-  public static $modules = array('entity_test', 'field_ui');
+  public static $modules = array('field_ui_test');
 
   /**
    * {@inheritdoc}
@@ -34,8 +34,10 @@ protected function setUp() {
    * Ensures that entity types with bundles do not break following entity types.
    */
   public function testFieldUIRoutes() {
-    $this->drupalGet('entity_test_no_id/structure/entity_test/fields');
-    $this->assertText('No fields are present yet.');
+    $this->drupalGet('field-ui-test-no-bundle/manage/fields');
+    // @todo Bring back this assertion in https://drupal.org/node/1963340.
+    // @see \Drupal\field_ui\FieldOverview::getRegions()
+    //$this->assertText('No fields are present yet.');
 
     $this->drupalGet('admin/config/people/accounts/fields');
     $this->assertTitle('Manage fields | Drupal');
diff --git a/core/modules/field_ui/src/Tests/FieldUiTestTrait.php b/core/modules/field_ui/src/Tests/FieldUiTestTrait.php
index 8b14eda..d20fd91 100644
--- a/core/modules/field_ui/src/Tests/FieldUiTestTrait.php
+++ b/core/modules/field_ui/src/Tests/FieldUiTestTrait.php
@@ -34,28 +34,28 @@
   public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $field_type = 'test_field', array $storage_edit = array(), array $field_edit = array()) {
     $label = $label ?: $this->randomString();
     $initial_edit = array(
-      'new_storage_type' => $field_type,
-      'label' => $label,
-      'field_name' => $field_name,
+      'fields[_add_new_field][field_name]' => $field_name,
+      'fields[_add_new_field][type]' => $field_type,
+      'fields[_add_new_field][label]' => $label,
     );
 
     // Allow the caller to set a NULL path in case they navigated to the right
     // page before calling this method.
     if ($bundle_path !== NULL) {
-      $bundle_path = "$bundle_path/fields/add-field";
+      $bundle_path = "$bundle_path/fields";
     }
 
-    // First step: 'Add field' page.
-    $this->drupalPostForm($bundle_path,  $initial_edit, t('Save and continue'));
+    // First step : 'Add new field' on the 'Manage fields' page.
+    $this->drupalPostForm($bundle_path,  $initial_edit, t('Save'));
     $this->assertRaw(t('These settings apply to the %label field everywhere it is used.', array('%label' => $label)), 'Storage settings page was displayed.');
     // Test Breadcrumbs.
     $this->assertLink($label, 0, 'Field label is correct in the breadcrumb of the storage settings page.');
 
-    // Second step: 'Storage settings' form.
+    // Second step : 'Storage settings' form.
     $this->drupalPostForm(NULL, $storage_edit, t('Save field settings'));
     $this->assertRaw(t('Updated field %label field settings.', array('%label' => $label)), 'Redirected to field settings page.');
 
-    // Third step: 'Field settings' form.
+    // Third step : 'Field settings' form.
     $this->drupalPostForm(NULL, $field_edit, t('Save settings'));
     $this->assertRaw(t('Saved %label configuration.', array('%label' => $label)), 'Redirected to "Manage fields" page.');
 
@@ -68,7 +68,7 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi
    *
    * @param string $bundle_path
    *   Admin path of the bundle that the field is to be attached to.
-   * @param string $existing_storage_name
+   * @param string $existing_field_name
    *   The name of the existing field storage for which we want to add a new
    *   field.
    * @param string $label
@@ -77,18 +77,18 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi
    *   (optional) $edit parameter for drupalPostForm() on the second step
    *   ('Field settings' form).
    */
-  public function fieldUIAddExistingField($bundle_path, $existing_storage_name, $label = NULL, array $field_edit = array()) {
+  public function fieldUIAddExistingField($bundle_path, $existing_field_name, $label = NULL, array $field_edit = array()) {
     $label = $label ?: $this->randomString();
     $initial_edit = array(
-      'existing_storage_name' => $existing_storage_name,
-      'existing_storage_label' => $label,
+      'fields[_add_existing_field][label]' => $label,
+      'fields[_add_existing_field][field_name]' => $existing_field_name,
     );
 
-    // First step: 'Re-use existing field' on the 'Add field' page.
-    $this->drupalPostForm("$bundle_path/fields/add-field", $initial_edit, t('Save and continue'));
+    // First step : 'Re-use existing field' on the 'Manage fields' page.
+    $this->drupalPostForm("$bundle_path/fields", $initial_edit, t('Save'));
     $this->assertNoRaw('&amp;lt;', 'The page does not have double escaped HTML tags.');
 
-    // Second step: 'Field settings' form.
+    // Second step : 'Field settings' form.
     $this->drupalPostForm(NULL, $field_edit, t('Save settings'));
     $this->assertRaw(t('Saved %label configuration.', array('%label' => $label)), 'Redirected to "Manage fields" page.');
 
diff --git a/core/modules/field_ui/src/Tests/ManageFieldsTest.php b/core/modules/field_ui/src/Tests/ManageFieldsTest.php
index 2a1161c..593d863 100644
--- a/core/modules/field_ui/src/Tests/ManageFieldsTest.php
+++ b/core/modules/field_ui/src/Tests/ManageFieldsTest.php
@@ -118,8 +118,11 @@ function manageFieldsPage($type = '') {
       $this->assertRaw($table_header . '</th>', format_string('%table_header table header was found.', array('%table_header' => $table_header)));
     }
 
-    // Test the "Add field" action link.
-    $this->assertLink('Add field');
+    // "Add new field" and "Re-use existing field" aren't a table heading so just
+    // test the text.
+    foreach (array('Add new field', 'Re-use existing field') as $element) {
+      $this->assertText($element, format_string('"@element" was found.', array('@element' => $element)));
+    }
 
     // Assert entity operations for all fields.
     $result = $this->xpath('//ul[@class = "dropbutton"]/li/a');
@@ -175,14 +178,12 @@ function updateField() {
    */
   function addExistingField() {
     // Check "Re-use existing field" appears.
-    $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
-    $this->assertRaw(t('Re-use an existing field'), '"Re-use existing field" was found.');
+    $this->drupalGet('admin/structure/types/manage/page/fields');
+    $this->assertRaw(t('Re-use existing field'), '"Re-use existing field" was found.');
 
     // Check that fields of other entity types (here, the 'comment_body' field)
     // do not show up in the "Re-use existing field" list.
-    $this->assertFalse($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value="comment"]'), 'The list of options respects entity type restrictions.');
-    // Validate the FALSE assertion above by also testing a valid one.
-    $this->assertTrue($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value=:field_name]', array(':field_name' => $this->field_name)), 'The list of options shows a valid option.');
+    $this->assertFalse($this->xpath('//select[@id="edit-add-existing-field-field-name"]//option[@value="comment"]'), 'The list of options respects entity type restrictions.');
 
     // Add a new field based on an existing field.
     $this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->field_name, $this->field_label . '_2');
@@ -259,8 +260,8 @@ protected function addPersistentFieldStorage() {
       $this->drupalPostForm(NULL, array(), t('Delete'));
     }
     // Check "Re-use existing field" appears.
-    $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
-    $this->assertRaw(t('Re-use an existing field'), '"Re-use existing field" was found.');
+    $this->drupalGet('admin/structure/types/manage/page/fields');
+    $this->assertRaw(t('Re-use existing field'), '"Re-use existing field" was found.');
     // Add a new field for the orphaned storage.
     $this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->field_name);
   }
@@ -301,11 +302,11 @@ function testFieldPrefix() {
 
     // Try to create the field.
     $edit = array(
-      'label' => $field_exceed_max_length_label,
-      'field_name' => $field_exceed_max_length_input,
+      'fields[_add_new_field][label]' => $field_exceed_max_length_label,
+      'fields[_add_new_field][field_name]' => $field_exceed_max_length_input,
     );
-    $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/fields/add-field', $edit, t('Save and continue'));
-    $this->assertText('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.');
+    $this->drupalPostForm('admin/structure/types/manage/' . $this->type . '/fields', $edit, t('Save'));
+    $this->assertText('New field name cannot be longer than 22 characters but is currently 23 characters long.');
 
     // Create a valid field.
     $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->type, $this->field_name_input, $this->field_label);
@@ -432,20 +433,20 @@ function testDisallowedFieldNames() {
 
     $label = 'Disallowed field';
     $edit = array(
-      'label' => $label,
-      'new_storage_type' => 'test_field',
+      'fields[_add_new_field][label]' => $label,
+      'fields[_add_new_field][type]' => 'test_field',
     );
 
     // Try with an entity key.
-    $edit['field_name'] = 'title';
+    $edit['fields[_add_new_field][field_name]'] = 'title';
     $bundle_path = 'admin/structure/types/manage/' . $this->type;
-    $this->drupalPostForm("$bundle_path/fields/add-field",  $edit, t('Save and continue'));
+    $this->drupalPostForm("$bundle_path/fields",  $edit, t('Save'));
     $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
 
     // Try with a base field.
-    $edit['field_name'] = 'sticky';
+    $edit['fields[_add_new_field][field_name]'] = 'sticky';
     $bundle_path = 'admin/structure/types/manage/' . $this->type;
-    $this->drupalPostForm("$bundle_path/fields/add-field",  $edit, t('Save and continue'));
+    $this->drupalPostForm("$bundle_path/fields",  $edit, t('Save'));
     $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
   }
 
@@ -490,10 +491,11 @@ function testLockedField() {
    * Tests that Field UI respects the 'no_ui' flag in the field type definition.
    */
   function testHiddenFields() {
+    $bundle_path = 'admin/structure/types/manage/' . $this->type . '/fields/';
+
     // Check that the field type is not available in the 'add new field' row.
-    $this->drupalGet('admin/structure/types/manage/' . $this->type . '/fields/add-field');
-    $this->assertFalse($this->xpath('//select[@id="edit-new-storage-type"]//option[@value="hidden_test_field"]'), "The 'add new field' select respects field types 'no_ui' property.");
-    $this->assertTrue($this->xpath('//select[@id="edit-new-storage-type"]//option[@value="shape"]'), "The 'add new field' select shows a valid option.");
+    $this->drupalGet($bundle_path);
+    $this->assertFalse($this->xpath('//select[@id="edit-fields-add-new-field-type"]//option[@value="hidden_test_field"]'), "The 'add new field' select respects field types 'no_ui' property.");
 
     // Create a field storage and a field programmatically.
     $field_name = 'hidden_test_field';
@@ -516,23 +518,23 @@ function testHiddenFields() {
 
     // Check that the newly added field appears on the 'Manage Fields'
     // screen.
-    $this->drupalGet('admin/structure/types/manage/' . $this->type . '/fields');
+    $this->drupalGet($bundle_path);
     $this->assertFieldByXPath('//table[@id="field-overview"]//tr[@id="hidden-test-field"]//td[1]', $field['label'], 'Field was created and appears in the overview page.');
 
     // Check that the field does not appear in the 're-use existing field' row
     // on other bundles.
-    $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
-    $this->assertFalse($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value=:field_name]', array(':field_name' => $field_name)), "The 're-use existing field' select respects field types 'no_ui' property.");
-    $this->assertTrue($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value=:field_name]', array(':field_name' => 'field_tags')), "The 're-use existing field' select shows a valid option.");
+    $bundle_path = 'admin/structure/types/manage/article/fields/';
+    $this->drupalGet($bundle_path);
+    $this->assertFalse($this->xpath('//select[@id="edit-add-existing-field-field-name"]//option[@value=:field_name]', array(':field_name' => $field_name)), "The 're-use existing field' select respects field types 'no_ui' property.");
 
     // Check that non-configurable fields are not available.
     $field_types = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
     foreach ($field_types as $field_type => $definition) {
       if (empty($definition['no_ui'])) {
-        $this->assertTrue($this->xpath('//select[@id="edit-new-storage-type"]//option[@value=:field_type]', array(':field_type' => $field_type)), String::format('Configurable field type @field_type is available.', array('@field_type' => $field_type)));
+        $this->assertTrue($this->xpath('//select[@id="edit-fields-add-new-field-type"]//option[@value=:field_type]', array(':field_type' => $field_type)), String::format('Configurable field type @field_type is available.', array('@field_type' => $field_type)));
       }
       else {
-        $this->assertFalse($this->xpath('//select[@id="edit-new-storage-type"]//option[@value=:field_type]', array(':field_type' => $field_type)), String::format('Non-configurable field type @field_type is not available.', array('@field_type' => $field_type)));
+        $this->assertFalse($this->xpath('//select[@id="edit-fields-add-new-field-type"]//option[@value=:field_type]', array(':field_type' => $field_type)), String::format('Non-configurable field type @field_type is not available.', array('@field_type' => $field_type)));
       }
     }
   }
@@ -557,12 +559,12 @@ function testDuplicateFieldName() {
     // field_tags already exists, so we're expecting an error when trying to
     // create a new field with the same name.
     $edit = array(
-      'field_name' => 'tags',
-      'label' => $this->randomMachineName(),
-      'new_storage_type' => 'taxonomy_term_reference',
+      'fields[_add_new_field][field_name]' => 'tags',
+      'fields[_add_new_field][label]' => $this->randomMachineName(),
+      'fields[_add_new_field][type]' => 'taxonomy_term_reference',
     );
-    $url = 'admin/structure/types/manage/' . $this->type . '/fields/add-field';
-    $this->drupalPostForm($url, $edit, t('Save and continue'));
+    $url = 'admin/structure/types/manage/' . $this->type . '/fields';
+    $this->drupalPostForm($url, $edit, t('Save'));
 
     $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
     $this->assertUrl($url, array(), 'Stayed on the same page.');
diff --git a/core/modules/field_ui/tests/modules/field_ui_test/field_ui_test.info.yml b/core/modules/field_ui/tests/modules/field_ui_test/field_ui_test.info.yml
new file mode 100644
index 0000000..9d7a5d1
--- /dev/null
+++ b/core/modules/field_ui/tests/modules/field_ui_test/field_ui_test.info.yml
@@ -0,0 +1,10 @@
+name: "Field UI tests"
+type: module
+description: "Support module for Field UI testing."
+package: Testing
+version: VERSION
+core: 8.x
+
+dependencies:
+  - field_ui
+  - entity_test
diff --git a/core/modules/field_ui/tests/modules/field_ui_test/src/Entity/FieldUITestNoBundle.php b/core/modules/field_ui/tests/modules/field_ui_test/src/Entity/FieldUITestNoBundle.php
new file mode 100644
index 0000000..ec75368
--- /dev/null
+++ b/core/modules/field_ui/tests/modules/field_ui_test/src/Entity/FieldUITestNoBundle.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field_ui_test\Entity\FieldUITestNoBundle.
+ */
+
+namespace Drupal\field_ui_test\Entity;
+
+use Drupal\entity_test\Entity\EntityTest;
+
+/**
+ * Defines the test Field UI class.
+ *
+ * @ContentEntityType(
+ *   id = "field_ui_test_no_bundle",
+ *   label = @Translation("Test Field UI entity, no bundle"),
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *   }
+ * )
+ */
+class FieldUITestNoBundle extends EntityTest {
+
+}
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index c5e6d41..d4be4f5 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -568,7 +568,7 @@ function template_preprocess_forum_list(&$variables) {
   // Sanitize each forum so that the template can safely print the data.
   foreach ($variables['forums'] as $id => $forum) {
     $variables['forums'][$id]->description = Xss::filterAdmin($forum->description->value);
-    $variables['forums'][$id]->link = forum_uri($forum);
+    $variables['forums'][$id]->link = $forum->url();
     $variables['forums'][$id]->name = String::checkPlain($forum->label());
     $variables['forums'][$id]->is_container = !empty($forum->forum_container->value);
     $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
diff --git a/core/modules/forum/src/Tests/ForumTest.php b/core/modules/forum/src/Tests/ForumTest.php
index dbd72b8..ce854bd 100644
--- a/core/modules/forum/src/Tests/ForumTest.php
+++ b/core/modules/forum/src/Tests/ForumTest.php
@@ -10,7 +10,6 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Link;
 use Drupal\simpletest\WebTestBase;
-use Drupal\Core\Url;
 
 /**
  * Create, view, edit, delete, and change forum entries and verify its
@@ -108,9 +107,9 @@ protected function setUp() {
    */
   function testForum() {
     //Check that the basic forum install creates a default forum topic
-    $this->drupalGet('/forum');
+    $this->drupalGet("/forum");
     // Look for the "General discussion" default forum
-    $this->assertRaw(t('<a href="'. Url::fromRoute('forum.page', ['taxonomy_term' => 1]) .'">General discussion</a>'), "Found the default forum at the /forum listing");
+    $this->assertText(t("General discussion"), "Found the default forum at the /forum listing");
 
     // Do the admin tests.
     $this->doAdminTests($this->admin_user);
diff --git a/core/modules/hal/src/Tests/DenormalizeTest.php b/core/modules/hal/src/Tests/DenormalizeTest.php
index 6931d1c..cbb593c 100644
--- a/core/modules/hal/src/Tests/DenormalizeTest.php
+++ b/core/modules/hal/src/Tests/DenormalizeTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\hal\Tests;
 
+use Drupal\Core\Url;
 use Symfony\Component\Serializer\Exception\UnexpectedValueException;
 
 /**
@@ -24,7 +25,7 @@ public function testTypeHandling() {
     $data_with_valid_type = array(
       '_links' => array(
         'type' => array(
-          'href' => _url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+          'href' => Url::fromUri('base://rest/type/entity_test/entity_test', array('absolute' => TRUE))->toString(),
         ),
       ),
     );
@@ -36,10 +37,10 @@ public function testTypeHandling() {
       '_links' => array(
         'type' => array(
           array(
-            'href' => _url('rest/types/foo', array('absolute' => TRUE)),
+            'href' => Url::fromUri('base://rest/types/foo', array('absolute' => TRUE))->toString(),
           ),
           array(
-            'href' => _url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+            'href' => Url::fromUri('base://rest/type/entity_test/entity_test', array('absolute' => TRUE))->toString(),
           ),
         ),
       ),
@@ -51,7 +52,7 @@ public function testTypeHandling() {
     $data_with_invalid_type = array(
       '_links' => array(
         'type' => array(
-          'href' => _url('rest/types/foo', array('absolute' => TRUE)),
+          'href' => Url::fromUri('base://rest/types/foo', array('absolute' => TRUE))->toString(),
         ),
       ),
     );
@@ -84,7 +85,7 @@ public function testMarkFieldForDeletion() {
     $no_field_data = array(
       '_links' => array(
         'type' => array(
-          'href' => _url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+          'href' => Url::fromUri('base://rest/type/entity_test/entity_test', array('absolute' => TRUE))->toString(),
         ),
       ),
     );
@@ -94,7 +95,7 @@ public function testMarkFieldForDeletion() {
     $empty_field_data = array(
       '_links' => array(
         'type' => array(
-          'href' => _url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+          'href' => Url::fromUri('base://rest/type/entity_test/entity_test', array('absolute' => TRUE))->toString(),
         ),
       ),
       'field_test_text' => array(),
@@ -112,7 +113,7 @@ public function testBasicFieldDenormalization() {
     $data = array(
       '_links' => array(
         'type' => array(
-          'href' => _url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+          'href' => Url::fromUri('base://rest/type/entity_test/entity_test', array('absolute' => TRUE))->toString(),
         ),
       ),
       'uuid' => array(
@@ -182,7 +183,7 @@ public function testPatchDenormailzation() {
     $data = array(
       '_links' => array(
         'type' => array(
-          'href' => _url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+          'href' => Url::fromUri('base://rest/type/entity_test/entity_test', array('absolute' => TRUE))->toString(),
         ),
       ),
       'field_test_text' => array(
diff --git a/core/modules/hal/src/Tests/FileNormalizeTest.php b/core/modules/hal/src/Tests/FileNormalizeTest.php
index d841ddf..2b50381 100644
--- a/core/modules/hal/src/Tests/FileNormalizeTest.php
+++ b/core/modules/hal/src/Tests/FileNormalizeTest.php
@@ -39,7 +39,8 @@ protected function setUp() {
     $this->installEntitySchema('file');
 
     $entity_manager = \Drupal::entityManager();
-    $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('default')), new RelationLinkManager(new MemoryBackend('default'), $entity_manager));
+    $url_assembler = \Drupal::service('unrouted_url_assembler');
+    $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('default'), $url_assembler), new RelationLinkManager(new MemoryBackend('default'), $entity_manager, $url_assembler));
 
     // Set up the mock serializer.
     $normalizers = array(
diff --git a/core/modules/hal/src/Tests/NormalizeTest.php b/core/modules/hal/src/Tests/NormalizeTest.php
index ee40c25..183430f 100644
--- a/core/modules/hal/src/Tests/NormalizeTest.php
+++ b/core/modules/hal/src/Tests/NormalizeTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\hal\Tests;
 
+use Drupal\Core\Url;
+
 /**
  * Tests that entities can be normalized in HAL.
  *
@@ -59,8 +61,8 @@ public function testNormalize() {
     $entity->getTranslation('en')->set('field_test_entity_reference', array(0 => $translation_values['field_test_entity_reference']));
     $entity->save();
 
-    $type_uri = _url('rest/type/entity_test/entity_test', array('absolute' => TRUE));
-    $relation_uri = _url('rest/relation/entity_test/entity_test/field_test_entity_reference', array('absolute' => TRUE));
+    $type_uri = Url::fromUri('base://rest/type/entity_test/entity_test', array('absolute' => TRUE))->toString();
+    $relation_uri = Url::fromUri('base://rest/relation/entity_test/entity_test/field_test_entity_reference', array('absolute' => TRUE))->toString();
 
     $expected_array = array(
       '_links' => array(
diff --git a/core/modules/hal/src/Tests/NormalizerTestBase.php b/core/modules/hal/src/Tests/NormalizerTestBase.php
index 5e72182..43f10b1 100644
--- a/core/modules/hal/src/Tests/NormalizerTestBase.php
+++ b/core/modules/hal/src/Tests/NormalizerTestBase.php
@@ -134,7 +134,8 @@ protected function setUp() {
     ))->save();
 
     $entity_manager = \Drupal::entityManager();
-    $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('default')), new RelationLinkManager(new MemoryBackend('default'), $entity_manager));
+    $url_assembler = \Drupal::service('unrouted_url_assembler');
+    $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend('default'), $url_assembler), new RelationLinkManager(new MemoryBackend('default'), $entity_manager, $url_assembler));
 
     $chain_resolver = new ChainEntityResolver(array(new UuidResolver($entity_manager), new TargetIdResolver()));
 
diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php
index a57904d..b5d1221 100644
--- a/core/modules/image/src/Entity/ImageStyle.php
+++ b/core/modules/image/src/Entity/ImageStyle.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
 use Drupal\Core\Routing\RequestHelper;
 use Drupal\Core\Site\Settings;
+use Drupal\Core\Url;
 use Drupal\image\ImageEffectPluginCollection;
 use Drupal\image\ImageEffectInterface;
 use Drupal\image\ImageStyleInterface;
@@ -222,7 +223,7 @@ public function buildUrl($path, $clean_urls = NULL) {
     // actual file path, this avoids bootstrapping PHP once the files are built.
     if ($clean_urls === FALSE && file_uri_scheme($uri) == 'public' && !file_exists($uri)) {
       $directory_path = file_stream_wrapper_get_instance_by_uri($uri)->getDirectoryPath();
-      return _url($directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query));
+      return Url::fromUri('base://' . $directory_path . '/' . file_uri_target($uri), array('absolute' => TRUE, 'query' => $token_query))->toString();
     }
 
     $file_url = file_create_url($uri);
diff --git a/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php
index 37611cd..303479f 100644
--- a/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php
+++ b/core/modules/language/src/Tests/LanguageUILanguageNegotiationTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\language\Tests;
 
+use Drupal\Core\Url;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationBrowser;
 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationSelected;
@@ -462,23 +463,22 @@ function testLanguageDomain() {
     // Test URL in another language: http://it.example.com/admin.
     // Base path gives problems on the testbot, so $correct_link is hard-coded.
     // @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test).
-    $italian_url = _url('admin', array('language' => $languages['it'], 'script' => ''));
+    $italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString();
     $url_scheme = \Drupal::request()->isSecure() ? 'https://' : 'http://';
     $correct_link = $url_scheme . $link;
-    $this->assertEqual($italian_url, $correct_link, format_string('The _url() function returns the right URL (@url) in accordance with the chosen language', array('@url' => $italian_url)));
+    $this->assertEqual($italian_url, $correct_link, format_string('The Url::fromRoute() returns the right URL (@url) in accordance with the chosen language', array('@url' => $italian_url)));
 
     // Test HTTPS via options.
-    $italian_url = _url('admin', array('https' => TRUE, 'language' => $languages['it'], 'script' => ''));
+    $italian_url = Url::fromRoute('system.admin', [], ['https' => TRUE, 'language' => $languages['it']])->toString();
     $correct_link = 'https://' . $link;
     $this->assertTrue($italian_url == $correct_link, format_string('The _url() function returns the right HTTPS URL (via options) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
 
     // Test HTTPS via current URL scheme.
     $request = Request::create('', 'GET', array(), array(), array(), array('HTTPS' => 'on'));
     $this->container->get('request_stack')->push($request);
-    $generator = $this->container->get('url_generator');
-    $italian_url = _url('admin', array('language' => $languages['it'], 'script' => ''));
+    $italian_url = Url::fromRoute('system.admin', [], ['language' => $languages['it']])->toString();
     $correct_link = 'https://' . $link;
-    $this->assertTrue($italian_url == $correct_link, format_string('The _url() function returns the right URL (via current URL scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
+    $this->assertTrue($italian_url == $correct_link, format_string('The Url::fromRoute() method returns the right URL (via current URL scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
   }
 
   /**
diff --git a/core/modules/language/src/Tests/LanguageUrlRewritingTest.php b/core/modules/language/src/Tests/LanguageUrlRewritingTest.php
index 32a8649..3de4f67 100644
--- a/core/modules/language/src/Tests/LanguageUrlRewritingTest.php
+++ b/core/modules/language/src/Tests/LanguageUrlRewritingTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Url;
 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
 use Drupal\simpletest\WebTestBase;
 use Symfony\Component\HttpFoundation\Request;
@@ -131,21 +132,21 @@ function testDomainNameNegotiationPort() {
 
     // Create an absolute French link.
     $language = \Drupal::languageManager()->getLanguage('fr');
-    $url = _url('', array(
+    $url = Url::fromRoute('<none>', [], [
       'absolute' => TRUE,
       'language' => $language,
-    ));
+    ])->toString();
 
     $expected = ($index_php ? 'http://example.fr:88/index.php' : 'http://example.fr:88') . rtrim(base_path(), '/') . '/';
 
     $this->assertEqual($url, $expected, 'The right port is used.');
 
     // If we set the port explicitly in _url(), it should not be overriden.
-    $url = _url('', array(
+    $url = Url::fromRoute('<none>', [], [
       'absolute' => TRUE,
       'language' => $language,
       'base_url' => $request->getBaseUrl() . ':90',
-    ));
+    ])->toString();
 
     $expected = $index_php ? 'http://example.fr:90/index.php' : 'http://example.fr:90' . rtrim(base_path(), '/') . '/';
 
diff --git a/core/modules/locale/src/Tests/LocaleUpdateBase.php b/core/modules/locale/src/Tests/LocaleUpdateBase.php
index 37d4d78..452d120 100644
--- a/core/modules/locale/src/Tests/LocaleUpdateBase.php
+++ b/core/modules/locale/src/Tests/LocaleUpdateBase.php
@@ -8,6 +8,7 @@
 namespace Drupal\locale\Tests;
 
 use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 use Drupal\Component\Utility\String;
 
@@ -53,7 +54,7 @@ protected function setUp() {
     // Update module should not go out to d.o to check for updates. We override
     // the url to the default update_test xml path. But without providing
     // a mock xml file, no update data will be found.
-    \Drupal::config('update.settings')->set('fetch.url', _url('update-test', array('absolute' => TRUE)))->save();
+    \Drupal::config('update.settings')->set('fetch.url', Url::fromRoute('update_test.update_test', [], ['absolute' => TRUE])->toString())->save();
 
     // Setup timestamps to identify old and new translation sources.
     $this->timestampOld = REQUEST_TIME - 300;
diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php
index 153e87c..81beed4 100644
--- a/core/modules/node/src/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/src/Tests/NodeTranslationUITest.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\content_translation\Tests\ContentTranslationUITest;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Url;
 
 /**
  * Tests the Node Translation UI.
@@ -291,7 +292,7 @@ function testTranslationRendering() {
     $this->doTestTranslations('node/' . $node->id(), $values);
 
     // Test that the node page has the correct alternate hreflang links.
-    $this->doTestAlternateHreflangLinks('node/' . $node->id());
+    $this->doTestAlternateHreflangLinks($node->urlInfo());
   }
 
   /**
@@ -313,16 +314,16 @@ protected function doTestTranslations($path, array $values) {
   /**
    * Tests that the given path provides the correct alternate hreflang links.
    *
-   * @param string $path
-   *   The path to be tested.
+   * @param \Drupal\Core\Url $url_info
+   *   The url info to be tested.
    */
-  protected function doTestAlternateHreflangLinks($path) {
+  protected function doTestAlternateHreflangLinks(Url $url_info) {
     $languages = $this->container->get('language_manager')->getLanguages();
     foreach ($this->langcodes as $langcode) {
-      $urls[$langcode] = _url($path, array('absolute' => TRUE, 'language' => $languages[$langcode]));
+      $urls[$langcode] = $url_info->setAbsolute()->setOption('language', $languages[$langcode])->toString();
     }
     foreach ($this->langcodes as $langcode) {
-      $this->drupalGet($path, array('language' => $languages[$langcode]));
+      $this->drupalGet($url_info->toString(), array('language' => $languages[$langcode]));
       foreach ($urls as $alternate_langcode => $url) {
         // Retrieve desired link elements from the HTML head.
         $links = $this->xpath('head/link[@rel = "alternate" and @href = :href and @hreflang = :hreflang]',
diff --git a/core/modules/node/src/Tests/NodeViewTest.php b/core/modules/node/src/Tests/NodeViewTest.php
index 426fec1..01a640d 100644
--- a/core/modules/node/src/Tests/NodeViewTest.php
+++ b/core/modules/node/src/Tests/NodeViewTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\node\Tests;
 
+use Drupal\Core\Url;
+
 /**
  * Tests the node/{node} page.
  *
@@ -24,13 +26,13 @@ public function testHtmlHeadLinks() {
     $this->drupalGet($node->getSystemPath());
 
     $result = $this->xpath('//link[@rel = "version-history"]');
-    $this->assertEqual($result[0]['href'], _url("node/{$node->id()}/revisions"));
+    $this->assertEqual($result[0]['href'], $node->url('version-history'));
 
     $result = $this->xpath('//link[@rel = "edit-form"]');
-    $this->assertEqual($result[0]['href'], _url("node/{$node->id()}/edit"));
+    $this->assertEqual($result[0]['href'], $node->url('edit-form'));
 
     $result = $this->xpath('//link[@rel = "canonical"]');
-    $this->assertEqual($result[0]['href'], _url("node/{$node->id()}"));
+    $this->assertEqual($result[0]['href'], $node->url());
   }
 
 }
diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml
index c25d692..14e4ab2 100644
--- a/core/modules/rest/rest.services.yml
+++ b/core/modules/rest/rest.services.yml
@@ -18,10 +18,10 @@ services:
     arguments: ['@rest.link_manager.type', '@rest.link_manager.relation']
   rest.link_manager.type:
     class: Drupal\rest\LinkManager\TypeLinkManager
-    arguments: ['@cache.default']
+    arguments: ['@cache.default', '@unrouted_url_assembler']
   rest.link_manager.relation:
     class: Drupal\rest\LinkManager\RelationLinkManager
-    arguments: ['@cache.default', '@entity.manager']
+    arguments: ['@cache.default', '@entity.manager', '@unrouted_url_assembler']
   rest.resource_routes:
     class: Drupal\rest\Routing\ResourceRoutes
     arguments: ['@plugin.manager.rest', '@config.factory', '@logger.channel.rest']
diff --git a/core/modules/rest/src/LinkManager/RelationLinkManager.php b/core/modules/rest/src/LinkManager/RelationLinkManager.php
index a3e483b..54bec53 100644
--- a/core/modules/rest/src/LinkManager/RelationLinkManager.php
+++ b/core/modules/rest/src/LinkManager/RelationLinkManager.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
 
 class RelationLinkManager implements RelationLinkManagerInterface {
 
@@ -27,24 +28,33 @@ class RelationLinkManager implements RelationLinkManagerInterface {
   protected $entityManager;
 
   /**
+   * The unrouted URL assembler.
+   *
+   * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
+   */
+  protected $urlAssembler;
+
+  /**
    * Constructor.
    *
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
    *   The cache of relation URIs and their associated Typed Data IDs.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
+   * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface $url_assembler
+   *   The unrouted URL assembler.
    */
-  public function __construct(CacheBackendInterface $cache, EntityManagerInterface $entity_manager) {
+  public function __construct(CacheBackendInterface $cache, EntityManagerInterface $entity_manager, UnroutedUrlAssemblerInterface $url_assembler) {
     $this->cache = $cache;
     $this->entityManager = $entity_manager;
+    $this->urlAssembler = $url_assembler;
   }
 
   /**
    * {@inheritdoc}
    */
   public function getRelationUri($entity_type, $bundle, $field_name) {
-    // @todo Make the base path configurable.
-    return _url("rest/relation/$entity_type/$bundle/$field_name", array('absolute' => TRUE));
+    return $this->urlAssembler->assemble("base://rest/relation/$entity_type/$bundle/$field_name", array('absolute' => TRUE));
   }
 
   /**
diff --git a/core/modules/rest/src/LinkManager/TypeLinkManager.php b/core/modules/rest/src/LinkManager/TypeLinkManager.php
index 98ac3d6..5a7e603 100644
--- a/core/modules/rest/src/LinkManager/TypeLinkManager.php
+++ b/core/modules/rest/src/LinkManager/TypeLinkManager.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
 
 class TypeLinkManager implements TypeLinkManagerInterface {
 
@@ -20,13 +21,23 @@ class TypeLinkManager implements TypeLinkManagerInterface {
   protected $cache;
 
   /**
+   * The unrouted URL assembler.
+   *
+   * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
+   */
+  protected $urlAssembler;
+
+  /**
    * Constructor.
    *
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache
    *   The injected cache backend for caching type URIs.
+   * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface $url_assembler
+   *   The unrouted URL assembler.
    */
-  public function __construct(CacheBackendInterface $cache) {
+  public function __construct(CacheBackendInterface $cache, UnroutedUrlAssemblerInterface $url_assembler) {
     $this->cache = $cache;
+    $this->urlAssembler = $url_assembler;
   }
 
   /**
@@ -42,7 +53,7 @@ public function __construct(CacheBackendInterface $cache) {
    */
   public function getTypeUri($entity_type, $bundle) {
     // @todo Make the base path configurable.
-    return _url("rest/type/$entity_type/$bundle", array('absolute' => TRUE));
+    return $this->urlAssembler->assemble("base://rest/type/$entity_type/$bundle", array('absolute' => TRUE));
   }
 
   /**
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index b7d0264..e3f4bd3 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Url;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\ResourceResponse;
 use Drupal\Component\Utility\String;
@@ -99,7 +100,7 @@ public function post(EntityInterface $entity = NULL) {
       $entity->save();
       $this->logger->notice('Created entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id()));
 
-      $url = _url(strtr($this->pluginId, ':', '/') . '/' . $entity->id(), array('absolute' => TRUE));
+      $url = Url::fromUri('base://' . strtr($this->pluginId, ':', '/') . '/' . $entity->id(), ['absolute' => TRUE])->toString();
       // 201 Created responses have an empty body.
       return new ResourceResponse(NULL, 201, array('Location' => $url));
     }
diff --git a/core/modules/rest/src/Tests/AuthTest.php b/core/modules/rest/src/Tests/AuthTest.php
index bf51c00..b461979 100644
--- a/core/modules/rest/src/Tests/AuthTest.php
+++ b/core/modules/rest/src/Tests/AuthTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\rest\Tests;
 
+use Drupal\Core\Url;
 use Drupal\rest\Tests\RESTTestBase;
 
 /**
@@ -87,7 +88,7 @@ protected function basicAuthGet($path, $username, $password) {
     $out = $this->curlExec(
       array(
         CURLOPT_HTTPGET => TRUE,
-        CURLOPT_URL => _url($path, array('absolute' => TRUE)),
+        CURLOPT_URL => Url::fromUri('base://' . $path, array('absolute' => TRUE))->toString(),
         CURLOPT_NOBODY => FALSE,
         CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
         CURLOPT_USERPWD => $username . ':' . $password,
diff --git a/core/modules/rest/src/Tests/CsrfTest.php b/core/modules/rest/src/Tests/CsrfTest.php
index 7ff6389..f087b65 100644
--- a/core/modules/rest/src/Tests/CsrfTest.php
+++ b/core/modules/rest/src/Tests/CsrfTest.php
@@ -5,6 +5,8 @@
 
 namespace Drupal\rest\Tests;
 
+use Drupal\Core\Url;
+
 /**
  * Tests the CSRF protection.
  *
@@ -107,7 +109,7 @@ protected function getCurlOptions() {
       CURLOPT_HTTPGET => FALSE,
       CURLOPT_POST => TRUE,
       CURLOPT_POSTFIELDS => $this->serialized,
-      CURLOPT_URL => _url('entity/' . $this->testEntityType, array('absolute' => TRUE)),
+      CURLOPT_URL => Url::fromUri('base://entity/' . $this->testEntityType, array('absolute' => TRUE))->toString(),
       CURLOPT_NOBODY => FALSE,
       CURLOPT_HTTPHEADER => array(
         "Content-Type: {$this->defaultMimeType}",
diff --git a/core/modules/rest/src/Tests/NodeTest.php b/core/modules/rest/src/Tests/NodeTest.php
index 71d4c33..48e6ab2 100644
--- a/core/modules/rest/src/Tests/NodeTest.php
+++ b/core/modules/rest/src/Tests/NodeTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\rest\Tests;
 
+use Drupal\Core\Url;
 use Drupal\rest\Tests\RESTTestBase;
 
 /**
@@ -69,7 +70,7 @@ public function testNodes() {
     $data = array(
       '_links' => array(
         'type' => array(
-          'href' => _url('rest/type/node/resttest', array('absolute' => TRUE)),
+          'href' => Url::fromUri('base://rest/type/node/resttest', array('absolute' => TRUE))->toString(),
         ),
       ),
       'title' => array(
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index 105df19..146b056 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -8,6 +8,7 @@
 namespace Drupal\rest\Tests;
 
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -62,7 +63,7 @@ protected function setUp() {
   /**
    * Helper function to issue a HTTP request with simpletest's cURL.
    *
-   * @param string $url
+   * @param string $path
    *   The relative URL, e.g. "entity/node/1"
    * @param string $method
    *   HTTP method, one of GET, POST, PUT or DELETE.
@@ -71,7 +72,7 @@ protected function setUp() {
    * @param string $mime_type
    *   The MIME type of the transmitted content.
    */
-  protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
+  protected function httpRequest($path, $method, $body = NULL, $mime_type = NULL) {
     if (!isset($mime_type)) {
       $mime_type = $this->defaultMimeType;
     }
@@ -79,14 +80,19 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
       // GET the CSRF token first for writing requests.
       $token = $this->drupalGet('rest/session/token');
     }
+
+    $options = array('absolute' =>TRUE);
+
     switch ($method) {
       case 'GET':
         // Set query if there are additional GET parameters.
-        $options = isset($body) ? array('absolute' => TRUE, 'query' => $body) : array('absolute' => TRUE);
+        if (isset($body)) {
+          $options['query'] = $body;
+        }
         $curl_options = array(
           CURLOPT_HTTPGET => TRUE,
           CURLOPT_CUSTOMREQUEST => 'GET',
-          CURLOPT_URL => _url($url, $options),
+          CURLOPT_URL => Url::fromUri('base://' . $path, $options)->toString(),
           CURLOPT_NOBODY => FALSE,
           CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
         );
@@ -97,7 +103,7 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
           CURLOPT_HTTPGET => FALSE,
           CURLOPT_POST => TRUE,
           CURLOPT_POSTFIELDS => $body,
-          CURLOPT_URL => _url($url, array('absolute' => TRUE)),
+          CURLOPT_URL => Url::fromUri('base://' . $path, $options)->toString(),
           CURLOPT_NOBODY => FALSE,
           CURLOPT_HTTPHEADER => array(
             'Content-Type: ' . $mime_type,
@@ -111,7 +117,7 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
           CURLOPT_HTTPGET => FALSE,
           CURLOPT_CUSTOMREQUEST => 'PUT',
           CURLOPT_POSTFIELDS => $body,
-          CURLOPT_URL => _url($url, array('absolute' => TRUE)),
+          CURLOPT_URL => Url::fromUri('base://' . $path, $options)->toString(),
           CURLOPT_NOBODY => FALSE,
           CURLOPT_HTTPHEADER => array(
             'Content-Type: ' . $mime_type,
@@ -125,7 +131,7 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
           CURLOPT_HTTPGET => FALSE,
           CURLOPT_CUSTOMREQUEST => 'PATCH',
           CURLOPT_POSTFIELDS => $body,
-          CURLOPT_URL => _url($url, array('absolute' => TRUE)),
+          CURLOPT_URL => Url::fromUri('base://' . $path, $options)->toString(),
           CURLOPT_NOBODY => FALSE,
           CURLOPT_HTTPHEADER => array(
             'Content-Type: ' . $mime_type,
@@ -138,7 +144,7 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
         $curl_options = array(
           CURLOPT_HTTPGET => FALSE,
           CURLOPT_CUSTOMREQUEST => 'DELETE',
-          CURLOPT_URL => _url($url, array('absolute' => TRUE)),
+          CURLOPT_URL => Url::fromUri('base://' . $path, $options)->toString(),
           CURLOPT_NOBODY => FALSE,
           CURLOPT_HTTPHEADER => array('X-CSRF-Token: ' . $token),
         );
@@ -149,7 +155,7 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
     $headers = $this->drupalGetHeaders();
     $headers = implode("\n", $headers);
 
-    $this->verbose($method . ' request to: ' . $url .
+    $this->verbose($method . ' request to: ' . $path .
       '<hr />Code: ' . curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE) .
       '<hr />Response headers: ' . $headers .
       '<hr />Response body: ' . $response);
diff --git a/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php b/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php
index 749628f..1674dd0 100644
--- a/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php
+++ b/core/modules/search/src/Tests/SearchConfigSettingsFormTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\search\Tests;
 
+use Drupal\Core\Url;
+
 /**
  * Verify the search config settings form.
  *
@@ -19,7 +21,7 @@ class SearchConfigSettingsFormTest extends SearchTestBase {
    *
    * @var array
    */
-  public static $modules = array('block', 'search_extra_type');
+  public static $modules = array('block', 'search_extra_type', 'test_page_test');
 
   /**
    * User who can search and administer search.
@@ -269,8 +271,8 @@ public function testMultipleSearchPages() {
     // Ensure both search pages have their tabs displayed.
     $this->drupalGet('search');
     $elements = $this->xpath('//*[contains(@class, :class)]//a', array(':class' => 'tabs primary'));
-    $this->assertIdentical((string) $elements[0]['href'], _url('search/' . $first['path']));
-    $this->assertIdentical((string) $elements[1]['href'], _url('search/' . $second['path']));
+    $this->assertIdentical((string) $elements[0]['href'], Url::fromRoute('search.view_' . $first_id)->toString());
+    $this->assertIdentical((string) $elements[1]['href'], Url::fromRoute('search.view_' . $second_id)->toString());
 
     // Switch the weight of the search pages and check the order of the tabs.
     $edit = array(
@@ -280,8 +282,8 @@ public function testMultipleSearchPages() {
     $this->drupalPostForm('admin/config/search/pages', $edit, t('Save configuration'));
     $this->drupalGet('search');
     $elements = $this->xpath('//*[contains(@class, :class)]//a', array(':class' => 'tabs primary'));
-    $this->assertIdentical((string) $elements[0]['href'], _url('search/' . $second['path']));
-    $this->assertIdentical((string) $elements[1]['href'], _url('search/' . $first['path']));
+    $this->assertIdentical((string) $elements[0]['href'], Url::fromRoute('search.view_' . $second_id)->toString());
+    $this->assertIdentical((string) $elements[1]['href'], Url::fromRoute('search.view_' . $first_id)->toString());
 
     // Check the initial state of the search pages.
     $this->drupalGet('admin/config/search/pages');
diff --git a/core/modules/search/src/Tests/SearchKeywordsConditionsTest.php b/core/modules/search/src/Tests/SearchKeywordsConditionsTest.php
index 789687f..2b023a9 100644
--- a/core/modules/search/src/Tests/SearchKeywordsConditionsTest.php
+++ b/core/modules/search/src/Tests/SearchKeywordsConditionsTest.php
@@ -23,7 +23,7 @@ class SearchKeywordsConditionsTest extends SearchTestBase {
    *
    * @var array
    */
-  public static $modules = array('comment', 'search_extra_type');
+  public static $modules = array('comment', 'search_extra_type', 'test_page_test');
 
   protected function setUp() {
     parent::setUp();
diff --git a/core/modules/search/tests/modules/search_extra_type/src/Plugin/Search/SearchExtraTypeSearch.php b/core/modules/search/tests/modules/search_extra_type/src/Plugin/Search/SearchExtraTypeSearch.php
index d1855ad..fc358d0 100644
--- a/core/modules/search/tests/modules/search_extra_type/src/Plugin/Search/SearchExtraTypeSearch.php
+++ b/core/modules/search/tests/modules/search_extra_type/src/Plugin/Search/SearchExtraTypeSearch.php
@@ -9,6 +9,8 @@
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\UrlGeneratorTrait;
+use Drupal\Core\Url;
 use Drupal\search\Plugin\ConfigurableSearchPluginBase;
 
 /**
@@ -21,6 +23,8 @@
  */
 class SearchExtraTypeSearch extends ConfigurableSearchPluginBase {
 
+  use UrlGeneratorTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -57,7 +61,7 @@ public function execute() {
     }
     return array(
       array(
-        'link' => _url('node'),
+        'link' => Url::fromRoute('test_page_test.test_page')->toString(),
         'type' => 'Dummy result type',
         'title' => 'Dummy title',
         'snippet' => SafeMarkup::set("Dummy search snippet to display. Keywords: {$this->keywords}\n\nConditions: " . print_r($this->searchParameters, TRUE)),
diff --git a/core/modules/serialization/src/Tests/EntityResolverTest.php b/core/modules/serialization/src/Tests/EntityResolverTest.php
index 2bf4ed9..e87b69b 100644
--- a/core/modules/serialization/src/Tests/EntityResolverTest.php
+++ b/core/modules/serialization/src/Tests/EntityResolverTest.php
@@ -6,6 +6,8 @@
 
 namespace Drupal\serialization\Tests;
 
+use Drupal\Core\Url;
+
 /**
  * Tests that entities references can be resolved.
  *
@@ -30,6 +32,9 @@ class EntityResolverTest extends NormalizerTestBase {
   protected function setUp() {
     parent::setUp();
 
+    $this->installSchema('system', 'router');
+    \Drupal::service('router.builder')->rebuild();
+
     // Create the test field storage.
     entity_create('field_storage_config', array(
       'entity_type' => 'entity_test_mulrev',
@@ -58,16 +63,16 @@ function testUuidEntityResolver() {
     $entity->set('field_test_entity_reference', array(array('target_id' => 1)));
     $entity->save();
 
-    $field_uri = _url('rest/relation/entity_test_mulrev/entity_test_mulrev/field_test_entity_reference', array('absolute' => TRUE));
+    $field_uri = Url::fromUri('base://rest/relation/entity_test_mulrev/entity_test_mulrev/field_test_entity_reference', array('absolute' => TRUE))->toString();
 
     $data = array(
       '_links' => array(
         'type' => array(
-          'href' => _url('rest/type/entity_test_mulrev/entity_test_mulrev', array('absolute' => TRUE)),
+          'href' => Url::fromUri('base://rest/type/entity_test_mulrev/entity_test_mulrev', array('absolute' => TRUE))->toString(),
         ),
         $field_uri => array(
           array(
-            'href' => _url('entity/entity_test_mulrev/' . $entity->id()),
+            'href' => $entity->url(),
           ),
         ),
       ),
@@ -75,7 +80,7 @@ function testUuidEntityResolver() {
         $field_uri => array(
           array(
             '_links' => array(
-              'self' => _url('entity/entity_test_mulrev/' . $entity->id()),
+              'self' => $entity->url(),
             ),
             'uuid' => array(
               array(
diff --git a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php
index a5b9703..fedd64c 100644
--- a/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php
+++ b/core/modules/simpletest/src/Tests/SimpleTestBrowserTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\simpletest\Tests;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -76,8 +77,7 @@ public function testInternalBrowser() {
     // @see drupal_valid_test_ua()
     // Not using File API; a potential error must trigger a PHP warning.
     unlink($this->siteDirectory . '/.htkey');
-    global $base_url;
-    $this->drupalGet(_url($base_url . '/core/install.php', array('external' => TRUE, 'absolute' => TRUE)));
+    $this->drupalGet(Url::fromUri('base://core/install.php', array('external' => TRUE, 'absolute' => TRUE))->toString());
     $this->assertResponse(403, 'Cannot access install.php.');
   }
 
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 2bd33fc..924d2dc 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -26,6 +26,7 @@
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PublicStream;
 use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Url;
 use Drupal\block\Entity\Block;
 use Symfony\Component\HttpFoundation\Request;
 use Drupal\user\Entity\Role;
@@ -1465,13 +1466,22 @@ protected function isInChildSite() {
   protected function drupalGet($path, array $options = array(), array $headers = array()) {
     $options['absolute'] = TRUE;
 
+    $url = $path;
+
+    // Assume that paths that start with a / are already fully generated
+    // URL's that are just missing $base_root.
+    if (isset($path[0]) && $path[0] == '/') {
+      $url = $GLOBALS['base_root'] . $path;
+    }
     // The URL generator service is not necessarily available yet; e.g., in
     // interactive installer tests.
-    if ($this->container->has('url_generator')) {
-      $url = $this->container->get('url_generator')->generateFromPath($path, $options);
-    }
-    else {
-      $url = $this->getAbsoluteUrl($path);
+    elseif ($this->container->has('url_generator')) {
+      if ($this->container->has('url_generator')) {
+        $url = $this->container->get('url_generator')->generateFromPath($path, $options);
+      }
+      else {
+        $url = $this->getAbsoluteUrl($path);
+      }
     }
 
     // We re-using a CURL connection here. If that connection still has certain
@@ -1958,11 +1968,22 @@ protected function drupalProcessAjaxResponse($content, array $ajax_response, arr
    *
    * @see WebTestBase::getAjaxPageStatePostData()
    * @see WebTestBase::curlExec()
-   * @see _url()
+   * @see \Drupal\Core\Url::fromUri()
    */
   protected function drupalPost($path, $accept, array $post, $options = array()) {
+    $options['absolute'] = TRUE;
+
+    // The URL generator service is not necessarily available yet; e.g., in
+    // interactive installer tests.
+    if ($this->container->has('url_generator')) {
+      $url = $this->container->get('url_generator')->generateFromPath($path, $options);
+    }
+    else {
+      $url = $this->getAbsoluteUrl($path);
+    }
+
     return $this->curlExec(array(
-      CURLOPT_URL => _url($path, $options + array('absolute' => TRUE)),
+      CURLOPT_URL => $url,
       CURLOPT_POST => TRUE,
       CURLOPT_POSTFIELDS => $this->serializePostValues($post),
       CURLOPT_HTTPHEADER => array(
diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module
index 53dd91f..951e24d 100644
--- a/core/modules/statistics/statistics.module
+++ b/core/modules/statistics/statistics.module
@@ -8,6 +8,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
 use Drupal\node\NodeInterface;
 
 /**
@@ -39,7 +40,7 @@ function statistics_help($route_name, RouteMatchInterface $route_match) {
 function statistics_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
   if (!$node->isNew() && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) {
     $build['statistics_content_counter']['#attached']['library'][] = 'statistics/drupal.statistics';
-    $settings = array('data' => array('nid' => $node->id()), 'url' => _url(drupal_get_path('module', 'statistics') . '/statistics.php'));
+    $settings = array('data' => array('nid' => $node->id()), 'url' => Url::fromUri('base://' . drupal_get_path('module', 'statistics') . '/statistics.php')->toString());
     $build['statistics_content_counter']['#attached']['drupalSettings']['statistics'] = $settings;
   }
 }
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index c3c25fa..16b5728 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -69,7 +69,6 @@ class DbUpdateController extends ControllerBase {
   protected $entityDefinitionUpdateManager;
 
   /**
-<<<<<<< ours
    * The bare HTML page renderer.
    *
    * @var \Drupal\Core\Render\BareHtmlPageRendererInterface
@@ -602,7 +601,7 @@ protected function triggerBatch(Request $request) {
     );
     batch_set($batch);
 
-    return batch_process('update.php/results', 'update.php/batch');
+    return batch_process('update.php/results', Url::fromUri('base://update.php/batch'));
   }
 
   /**
diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
index 1991a31..5abf4b1 100644
--- a/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
+++ b/core/modules/system/src/Tests/Cache/PageCacheTagsIntegrationTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Cache;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 use Drupal\Core\Cache\Cache;
 
@@ -67,7 +68,7 @@ function testPageCacheTags() {
     ));
 
     // Full node page 1.
-    $this->verifyPageCacheTags('node/' . $node_1->id(), array(
+    $this->verifyPageCacheTags($node_1->urlInfo(), array(
       'rendered',
       'theme:bartik',
       'theme_global_settings',
@@ -97,7 +98,7 @@ function testPageCacheTags() {
     ));
 
     // Full node page 2.
-    $this->verifyPageCacheTags('node/' . $node_2->id(), array(
+    $this->verifyPageCacheTags($node_2->urlInfo(), array(
       'rendered',
       'theme:bartik',
       'theme_global_settings',
@@ -132,24 +133,24 @@ function testPageCacheTags() {
   /**
    * Fills page cache for the given path, verify cache tags on page cache hit.
    *
-   * @param $path
-   *   The Drupal page path to test.
+   * @param \Drupal\Core\Url $url
+   *   The url
    * @param $expected_tags
    *   The expected cache tags for the page cache entry of the given $path.
    */
-  protected function verifyPageCacheTags($path, $expected_tags) {
+  protected function verifyPageCacheTags(Url $url, $expected_tags) {
     sort($expected_tags);
-    $this->drupalGet($path);
+    $this->drupalGet($url->setAbsolute()->toString());
     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
     $actual_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
     sort($actual_tags);
     $this->assertIdentical($actual_tags, $expected_tags);
-    $this->drupalGet($path);
+    $this->drupalGet($url->setAbsolute()->toString());
     $actual_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
     sort($actual_tags);
     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
     $this->assertIdentical($actual_tags, $expected_tags);
-    $cid_parts = array(_url($path, array('absolute' => TRUE)), 'html');
+    $cid_parts = array($url->setAbsolute()->toString(), 'html');
     $cid = implode(':', $cid_parts);
     $cache_entry = \Drupal::cache('render')->get($cid);
     sort($cache_entry->tags);
diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php b/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php
index 864fd93..624c3ca 100644
--- a/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php
+++ b/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Cache;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 use Drupal\Component\Utility\String;
 
@@ -53,7 +54,7 @@ protected function verifyPageCache($path, $hit_or_miss, $tags = FALSE) {
     $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), $hit_or_miss, $message);
 
     if ($hit_or_miss === 'HIT' && is_array($tags)) {
-      $cid_parts = array(_url($path, array('absolute' => TRUE)), 'html');
+      $cid_parts = array(Url::fromUri('base://' . $path, array('absolute' => TRUE))->toString(), 'html');
       $cid = implode(':', $cid_parts);
       $cache_entry = \Drupal::cache('render')->get($cid);
       sort($cache_entry->tags);
diff --git a/core/modules/system/src/Tests/Common/AddFeedTest.php b/core/modules/system/src/Tests/Common/AddFeedTest.php
index 0f5a560..4fe5e6f 100644
--- a/core/modules/system/src/Tests/Common/AddFeedTest.php
+++ b/core/modules/system/src/Tests/Common/AddFeedTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Common;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -22,15 +23,15 @@ class AddFeedTest extends WebTestBase {
   function testBasicFeedAddNoTitle() {
     $path = $this->randomMachineName(12);
     $external_url = 'http://' . $this->randomMachineName(12) . '/' . $this->randomMachineName(12);
-    $fully_qualified_local_url = _url($this->randomMachineName(12), array('absolute' => TRUE));
+    $fully_qualified_local_url = Url::fromUri('base://' . $this->randomMachineName(12), array('absolute' => TRUE))->toString();
 
     $path_for_title = $this->randomMachineName(12);
     $external_for_title = 'http://' . $this->randomMachineName(12) . '/' . $this->randomMachineName(12);
-    $fully_qualified_for_title = _url($this->randomMachineName(12), array('absolute' => TRUE));
+    $fully_qualified_for_title = Url::fromUri('base://' . $this->randomMachineName(12), array('absolute' => TRUE))->toString();
 
     $urls = array(
       'path without title' => array(
-        'url' => _url($path, array('absolute' => TRUE)),
+        'url' => Url::fromUri('base://' . $path, array('absolute' => TRUE))->toString(),
         'title' => '',
       ),
       'external URL without title' => array(
@@ -42,7 +43,7 @@ function testBasicFeedAddNoTitle() {
         'title' => '',
       ),
       'path with title' => array(
-        'url' => _url($path_for_title, array('absolute' => TRUE)),
+        'url' => Url::fromUri('base://' . $path_for_title, array('absolute' => TRUE))->toString(),
         'title' => $this->randomMachineName(12),
       ),
       'external URL with title' => array(
diff --git a/core/modules/system/src/Tests/Common/RenderElementTypesTest.php b/core/modules/system/src/Tests/Common/RenderElementTypesTest.php
index 4484ea5..f757ef2 100644
--- a/core/modules/system/src/Tests/Common/RenderElementTypesTest.php
+++ b/core/modules/system/src/Tests/Common/RenderElementTypesTest.php
@@ -148,7 +148,7 @@ function testMoreLink() {
           '#type' => 'more_link',
           '#url' => Url::fromRoute('router_test.1'),
         ),
-        'expected' => '//div[@class="more-link"]/a[@href="' . _url('router_test/test1') . '" and text()="More"]',
+        'expected' => '//div[@class="more-link"]/a[@href="' . Url::fromRoute('router_test.1')->toString() . '" and text()="More"]',
       ),
       array(
         'name' => "#type 'more_link' anchor tag with a route",
@@ -165,7 +165,7 @@ function testMoreLink() {
           '#url' => Url::fromRoute('system.admin_content'),
           '#options' => array('absolute' => TRUE),
         ),
-        'expected' => '//div[@class="more-link"]/a[@href="' . _url('admin/content', array('absolute' => TRUE)) . '" and text()="More"]',
+        'expected' => '//div[@class="more-link"]/a[@href="' . Url::fromRoute('system.admin_content')->setAbsolute()->toString() . '" and text()="More"]',
       ),
       array(
         'name' => "#type 'more_link' anchor tag to the front page",
@@ -173,7 +173,7 @@ function testMoreLink() {
           '#type' => 'more_link',
           '#url' => Url::fromRoute('<front>'),
         ),
-        'expected' => '//div[@class="more-link"]/a[@href="' . _url('<front>') . '" and text()="More"]',
+        'expected' => '//div[@class="more-link"]/a[@href="' . Url::fromRoute('<front>')->toString() . '" and text()="More"]',
       ),
     );
 
diff --git a/core/modules/system/src/Tests/Common/UrlTest.php b/core/modules/system/src/Tests/Common/UrlTest.php
index 92342d2..ad9ce96 100644
--- a/core/modules/system/src/Tests/Common/UrlTest.php
+++ b/core/modules/system/src/Tests/Common/UrlTest.php
@@ -36,12 +36,12 @@ function testLinkXSS() {
     $text = $this->randomMachineName();
     $path = "<SCRIPT>alert('XSS')</SCRIPT>";
     $link = _l($text, $path);
-    $sanitized_path = check_url(_url($path));
+    $sanitized_path = check_url(Url::fromUri('base://' . $path)->toString());
     $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by _l().', array('@path' => $path)));
 
     // Test _url().
-    $link = _url($path);
-    $sanitized_path = check_url(_url($path));
+    $link = Url::fromUri('base://' . $path)->toString();
+    $sanitized_path = check_url(Url::fromUri('base://' . $path)->toString());
     $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered by #theme', ['@path' => $path]));
   }
 
@@ -91,20 +91,20 @@ function testLinkAttributes() {
     $path = 'common-test/type-link-active-class';
 
     $this->drupalGet($path, $options_no_query);
-    $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => _url($path, $options_no_query), ':class' => 'active'));
+    $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_no_query)->toString(), ':class' => 'active'));
     $this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page is marked active.');
 
-    $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => _url($path, $options_query), ':class' => 'active'));
+    $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_query)->toString(), ':class' => 'active'));
     $this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page with a query string when the current page has no query string is not marked active.');
 
     $this->drupalGet($path, $options_query);
-    $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => _url($path, $options_query), ':class' => 'active'));
+    $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_query)->toString(), ':class' => 'active'));
     $this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page with a query string that matches the current query string is marked active.');
 
-    $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => _url($path, $options_query_reverse), ':class' => 'active'));
+    $links = $this->xpath('//a[@href = :href and contains(@class, :class)]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_query_reverse)->toString(), ':class' => 'active'));
     $this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page with a query string that has matching parameters to the current query string but in a different order is marked active.');
 
-    $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => _url($path, $options_no_query), ':class' => 'active'));
+    $links = $this->xpath('//a[@href = :href and not(contains(@class, :class))]', array(':href' => Url::fromRoute('common_test.l_active_class', [], $options_no_query)->toString(), ':class' => 'active'));
     $this->assertTrue(isset($links[0]), 'A link generated by _l() to the current page without a query string when the current page has a query string is not marked active.');
 
     // Test adding a custom class in links produced by _l() and #type 'link'.
@@ -257,30 +257,30 @@ function testExternalUrls() {
 
     // Verify external URL can contain a fragment.
     $url = $test_url . '#drupal';
-    $result = _url($url);
+    $result = Url::fromUri($url)->toString();
     $this->assertEqual($url, $result, 'External URL with fragment works without a fragment in $options.');
 
     // Verify fragment can be overidden in an external URL.
     $url = $test_url . '#drupal';
     $fragment = $this->randomMachineName(10);
-    $result = _url($url, array('fragment' => $fragment));
+    $result = Url::fromUri($url, array('fragment' => $fragment))->toString();
     $this->assertEqual($test_url . '#' . $fragment, $result, 'External URL fragment is overidden with a custom fragment in $options.');
 
     // Verify external URL can contain a query string.
     $url = $test_url . '?drupal=awesome';
-    $result = _url($url);
+    $result = Url::fromUri($url)->toString();
     $this->assertEqual($url, $result, 'External URL with query string works without a query string in $options.');
 
     // Verify external URL can be extended with a query string.
     $url = $test_url;
     $query = array($this->randomMachineName(5) => $this->randomMachineName(5));
-    $result = _url($url, array('query' => $query));
+    $result = Url::fromUri($url, array('query' => $query))->toString();
     $this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, 'External URL can be extended with a query string in $options.');
 
     // Verify query string can be extended in an external URL.
     $url = $test_url . '?drupal=awesome';
     $query = array($this->randomMachineName(5) => $this->randomMachineName(5));
-    $result = _url($url, array('query' => $query));
+    $result = Url::fromUri($url, array('query' => $query))->toString();
     $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.');
   }
 }
diff --git a/core/modules/system/src/Tests/Form/RebuildTest.php b/core/modules/system/src/Tests/Form/RebuildTest.php
index a1d4ac2..76de748 100644
--- a/core/modules/system/src/Tests/Form/RebuildTest.php
+++ b/core/modules/system/src/Tests/Form/RebuildTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Form;
 
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -105,6 +106,6 @@ function testPreserveFormActionAfterAJAX() {
 
     // Ensure that the form's action is correct.
     $forms = $this->xpath('//form[contains(@class, "node-page-form")]');
-    $this->assert(count($forms) == 1 && $forms[0]['action'] == _url('node/add/page'), 'Re-rendered form contains the correct action value.');
+    $this->assert(count($forms) == 1 && $forms[0]['action'] == Url::fromRoute('node.add', ['node_type' => 'page'])->toString(), 'Re-rendered form contains the correct action value.');
   }
 }
diff --git a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
index ab0950c..6ed706a 100644
--- a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
+++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Url;
 use Drupal\node\Entity\NodeType;
 
 /**
@@ -23,13 +24,23 @@ class BreadcrumbTest extends MenuTestBase {
    *
    * @var array
    */
-  public static $modules = array('menu_test', 'block');
+  public static $modules = ['menu_test', 'block', 'node'];
 
   /**
    * Test paths in the Standard profile.
    */
   protected $profile = 'standard';
 
+  /**
+   * @var \Drupal\user\UserInterface
+   */
+  protected $admin_user;
+
+  /**
+   * @var \Drupal\user\UserInterface
+   */
+  protected $web_user;
+
   protected function setUp() {
     parent::setUp();
 
@@ -51,105 +62,132 @@ protected function setUp() {
    */
   function testBreadCrumbs() {
     // Prepare common base breadcrumb elements.
-    $home = array('' => 'Home');
-    $admin = $home + array('admin' => t('Administration'));
-    $config = $admin + array('admin/config' => t('Configuration'));
+    $home = [['url' => Url::fromRoute('<front>'), 'title' => 'Home']];
+    $admin = $home + [1 => [
+      'url' => Url::fromRoute('system.admin'),
+      'title' => t('Administration'),
+      ]];
+    $config = $admin + [2 => [
+      'url' => Url::fromRoute('system.admin_config'),
+      'title' => t('Configuration'),
+      ]];
     $type = 'article';
 
     // Verify Taxonomy administration breadcrumbs.
-    $trail = $admin + array(
-      'admin/structure' => t('Structure'),
-    );
-    $this->assertBreadcrumb('admin/structure/taxonomy', $trail);
-
-    $trail += array(
-      'admin/structure/taxonomy' => t('Taxonomy'),
-    );
-    $this->assertBreadcrumb('admin/structure/taxonomy/manage/tags', $trail);
-    $trail += array(
-      'admin/structure/taxonomy/manage/tags' => t('Tags'),
-    );
-    $this->assertBreadcrumb('admin/structure/taxonomy/manage/tags/overview', $trail);
-    $this->assertBreadcrumb('admin/structure/taxonomy/manage/tags/add', $trail);
+    $trail = $admin + [2 => [
+      'url' => Url::fromRoute('system.admin_structure'),
+      'title' => t('Structure'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('taxonomy.vocabulary_list'), $trail);
+
+    $trail += [3 => [
+      'url' => Url::fromRoute('taxonomy.vocabulary_list'),
+      'title' => t('Taxonomy'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => 'tags']), $trail);
+    $trail += [4 => [
+      'url' => Url::fromRoute('entity.taxonomy_vocabulary.edit_form', ['taxonomy_vocabulary' => 'tags']),
+      'title' => t('Tags'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('entity.taxonomy_vocabulary.overview_form', ['taxonomy_vocabulary' => 'tags']), $trail);
+    $this->assertBreadcrumb(Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => 'tags']), $trail);
 
     // Verify Menu administration breadcrumbs.
-    $trail = $admin + array(
-      'admin/structure' => t('Structure'),
-    );
-    $this->assertBreadcrumb('admin/structure/menu', $trail);
-
-    $trail += array(
-      'admin/structure/menu' => t('Menus'),
-    );
-    $this->assertBreadcrumb('admin/structure/menu/manage/tools', $trail);
-
-    $trail += array(
-      'admin/structure/menu/manage/tools' => t('Tools'),
-    );
-    $this->assertBreadcrumb("admin/structure/menu/link/node.add_page/edit", $trail);
-    $this->assertBreadcrumb('admin/structure/menu/manage/tools/add', $trail);
+    $trail = $admin + [2 => [
+      'url' => Url::fromRoute('system.admin_structure'),
+      'title' => t('Structure'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('menu_ui.overview_page'), $trail);
+
+    $trail += [3 => [
+      'url' => Url::fromRoute('menu_ui.overview_page'),
+      'title' => t('Menus'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('entity.menu.edit_form', ['menu' => 'tools']), $trail);
+
+    $trail += [4 => [
+      'url' => Url::fromRoute('entity.menu.edit_form', ['menu' => 'tools']),
+      'title' => t('Tools'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('menu_ui.link_edit', ['menu_link_plugin' => 'node.add_page']), $trail);
+    $this->assertBreadcrumb(Url::fromRoute('entity.menu.add_link_form', ['menu' => 'tools']), $trail);
 
     // Verify Node administration breadcrumbs.
-    $trail = $admin + array(
-      'admin/structure' => t('Structure'),
-      'admin/structure/types' => t('Content types'),
-    );
-    $this->assertBreadcrumb('admin/structure/types/add', $trail);
-    $this->assertBreadcrumb("admin/structure/types/manage/$type", $trail);
-    $trail += array(
-      "admin/structure/types/manage/$type" => t('Article'),
-    );
-    $this->assertBreadcrumb("admin/structure/types/manage/$type/fields", $trail);
-    $this->assertBreadcrumb("admin/structure/types/manage/$type/display", $trail);
-    $trail_teaser = $trail + array(
-      "admin/structure/types/manage/$type/display" => t('Manage display'),
-    );
-    $this->assertBreadcrumb("admin/structure/types/manage/$type/display/teaser", $trail_teaser);
-    $this->assertBreadcrumb("admin/structure/types/manage/$type/delete", $trail);
-    $trail += array(
-      "admin/structure/types/manage/$type/fields" => t('Manage fields'),
-    );
-    $this->assertBreadcrumb("admin/structure/types/manage/$type/fields/node.$type.body", $trail);
+    $trail = $admin + [2 =>
+      [
+        'url' => Url::fromRoute('system.admin_structure'),
+        'title' => t('Structure'),
+      ],
+      3 => [
+        'url' => Url::fromRoute('node.overview_types'),
+        'title' => t('Content types'),
+      ],
+    ];
+    $this->assertBreadcrumb(Url::fromRoute('node.type_add'), $trail);
+    $this->assertBreadcrumb(Url::fromRoute('entity.node_type.edit_form', ['node_type' => $type]), $trail);
+    $trail += [4 => [
+      'url' => Url::fromRoute('entity.node_type.edit_form', ['node_type' => $type]),
+      'title' => t('Article'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('field_ui.overview_node'), $trail);
+    $this->assertBreadcrumb(Url::fromRoute('field_ui.display_overview_node'), $trail);
+    $trail_teaser = $trail + [5 => [
+      'url' => Url::fromRoute('field_ui.display_overview_node'),
+      'title' => t('Manage display'),
+    ]];
+
+
+    $this->assertBreadcrumb(Url::fromRoute('field_ui.display_overview_view_mode_node', ['view_mode' => 'teaser']), $trail_teaser);
+    $this->assertBreadcrumb(Url::fromRoute('entity.node_type.delete_form', ['node_type' => $type]), $trail);
+    $trail += [6 => [
+      'url' => Url::fromRoute('field_ui.overview_node'),
+      'title' => t('Manage fields'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('field_ui.field_edit_node', ['field_config' => "node.$type.body"]), $trail);
 
     // Verify Filter text format administration breadcrumbs.
     $filter_formats = filter_formats();
     $format = reset($filter_formats);
     $format_id = $format->id();
-    $trail = $config + array(
-      'admin/config/content' => t('Content authoring'),
-    );
-    $this->assertBreadcrumb('admin/config/content/formats', $trail);
-
-    $trail += array(
-      'admin/config/content/formats' => t('Text formats and editors'),
-    );
-    $this->assertBreadcrumb('admin/config/content/formats/add', $trail);
-    $this->assertBreadcrumb("admin/config/content/formats/manage/$format_id", $trail);
+    $trail = $config + [3 => [
+      'url' => Url::fromRoute('system.admin_config_content'),
+      'title' => t('Content authoring'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('filter.admin_overview'), $trail);
+
+    $trail += [4 => [
+      'url' => Url::fromRoute('filter.admin_overview'),
+      'title' => t('Text formats and editors'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('filter.format_add'), $trail);
+    $this->assertBreadcrumb(Url::fromRoute('entity.filter_format.edit_form', ['filter_format' => $format_id]), $trail);
     // @todo Remove this part once we have a _title_callback, see
     //   https://drupal.org/node/2076085.
-    $trail += array(
-      "admin/config/content/formats/manage/$format_id" => $format->label(),
-    );
-    $this->assertBreadcrumb("admin/config/content/formats/manage/$format_id/disable", $trail);
+    $trail += [5 => [
+      'url' => Url::fromRoute('entity.filter_format.edit_form', ['filter_format' => $format_id]),
+      'title' => $format->label(),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('entity.filter_format.disable', ['filter_format' => $format_id]), $trail);
 
     // Verify node breadcrumbs (without menu link).
     $node1 = $this->drupalCreateNode();
     $nid1 = $node1->id();
     $trail = $home;
-    $this->assertBreadcrumb("node/$nid1", $trail);
+    $this->assertBreadcrumb($node1->urlInfo(), $trail);
     // Also verify that the node does not appear elsewhere (e.g., menu trees).
     $this->assertNoLink($node1->getTitle());
     // Also verify that the node does not appear elsewhere (e.g., menu trees).
     $this->assertNoLink($node1->getTitle());
 
-    $trail += array(
-      "node/$nid1" => $node1->getTitle(),
-    );
-    $this->assertBreadcrumb("node/$nid1/edit", $trail);
+    $trail += [1 => [
+      'url' => $node1->urlInfo(),
+      'title' => $node1->getTitle(),
+    ]];
+    $this->assertBreadcrumb($node1->urlInfo('edit-form'), $trail);
 
     // Verify that breadcrumb on node listing page contains "Home" only.
     $trail = array();
-    $this->assertBreadcrumb('node', $trail);
+    $this->assertBreadcrumb(Url::fromRoute('view.frontpage.page_1'), $trail);
 
     // Verify node breadcrumbs (in menu).
     // Do this separately for Main menu and Tools menu, since only the
@@ -197,17 +235,20 @@ function testBreadCrumbs() {
     $edit = array(
       'menu[menu_parent]' => $link->getMenuName() . ':' . $link->getPluginId(),
     );
-    $this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save and keep published'));
-    $expected = array(
-      "node" => $link->getTitle(),
-    );
+    $this->drupalPostForm($parent->url('edit-form'), $edit, t('Save and keep published'));
+    $expected = [1 => [
+      'url' => Url::fromRoute('view.frontpage.page_1'),
+      'title' => $link->getTitle(),
+    ]];
     $trail = $home + $expected;
-    $tree = $expected + array(
-      'node/' . $parent->id() => $parent->menu['title'],
-    );
-    $trail += array(
-      'node/' . $parent->id() => $parent->menu['title'],
-    );
+    $tree = $expected + [2 => [
+      'url' => $parent->urlInfo(),
+      'title' => $parent->menu['title'],
+    ]];
+    $trail += [2 => [
+      'url' => $parent->urlInfo(),
+      'title' => $parent->menu['title'],
+    ]];
 
     // Add a taxonomy term/tag to last node, and add a link for that term to the
     // Tools menu.
@@ -261,16 +302,19 @@ function testBreadCrumbs() {
     // Logout the user because we want to check the active class as well, which
     // is just rendered as anonymous user.
     $this->drupalLogout();
+    $i = 0;
     foreach ($tags as $name => $data) {
       $term = $data['term'];
       /** @var \Drupal\menu_link_content\MenuLinkContentInterface $link */
       $link = $data['link'];
 
-      $link_path = $link->getUrlObject()->getInternalPath();
-      $tree += array(
-        $link_path => $link->getTitle(),
-      );
-      $this->assertBreadcrumb($link_path, $trail, $term->getName(), $tree);
+      $url = $link->getUrlObject();
+      $link_path = $url->getInternalPath();
+      $tree += [$i++ => [
+        'url' => $url,
+        'title' => $link->getTitle(),
+      ]];
+      $this->assertBreadcrumb($url, $trail, $term->getName(), $tree);
       $this->assertEscaped($parent->getTitle(), 'Tagged node found.');
 
       // Additionally make sure that this link appears only once; i.e., the
@@ -279,15 +323,16 @@ function testBreadCrumbs() {
       // other than the breadcrumb trail.
       $elements = $this->xpath('//nav[@id=:menu]/descendant::a[@href=:href]', array(
         ':menu' => 'block-bartik-tools',
-        ':href' => _url($link_path),
+        ':href' => $url->toString(),
       ));
       $this->assertTrue(count($elements) == 1, "Link to {$link_path} appears only once.");
 
       // Next iteration should expect this tag as parent link.
       // Note: Term name, not link name, due to taxonomy_term_page().
-      $trail += array(
-        $link_path => $term->getName(),
-      );
+      $trail += [$i => [
+        'url' => $url,
+        'title' => $term->getName(),
+      ]];
     }
 
     // Verify breadcrumbs on user and user/%.
@@ -298,22 +343,23 @@ function testBreadCrumbs() {
     ));
 
     // Verify breadcrumb on front page.
-    $this->assertBreadcrumb('<front>', array());
+    $this->assertBreadcrumb(Url::fromRoute('<front>'), array());
 
     // Verify breadcrumb on user pages (without menu link) for anonymous user.
     $trail = $home;
-    $this->assertBreadcrumb('user', $trail, t('Log in'));
-    $this->assertBreadcrumb('user/' . $this->admin_user->id(), $trail, $this->admin_user->getUsername());
+    $this->assertBreadcrumb(Url::fromRoute('user.page'), $trail, t('Log in'));
+    $this->assertBreadcrumb($this->admin_user->urlInfo(), $trail, $this->admin_user->getUsername());
 
     // Verify breadcrumb on user pages (without menu link) for registered users.
     $this->drupalLogin($this->admin_user);
     $trail = $home;
-    $this->assertBreadcrumb('user', $trail, $this->admin_user->getUsername());
-    $this->assertBreadcrumb('user/' . $this->admin_user->id(), $trail, $this->admin_user->getUsername());
-    $trail += array(
-      'user/' . $this->admin_user->id() => $this->admin_user->getUsername(),
-    );
-    $this->assertBreadcrumb('user/' . $this->admin_user->id() . '/edit', $trail, $this->admin_user->getUsername());
+    $this->assertBreadcrumb(Url::fromRoute('user.page'), $trail, $this->admin_user->getUsername());
+    $this->assertBreadcrumb($this->admin_user->urlInfo(), $trail, $this->admin_user->getUsername());
+    $trail += [1 => [
+      'url' => $this->admin_user->urlInfo(),
+      'title' => $this->admin_user->getUserName(),
+    ]];
+    $this->assertBreadcrumb($this->admin_user->urlInfo('edit-form'), $trail, $this->admin_user->getUsername());
 
     // Create a second user to verify breadcrumb on user pages again.
     $this->web_user = $this->drupalCreateUser(array(
@@ -324,19 +370,21 @@ function testBreadCrumbs() {
 
     // Verify correct breadcrumb and page title on another user's account pages.
     $trail = $home;
-    $this->assertBreadcrumb('user/' . $this->admin_user->id(), $trail, $this->admin_user->getUsername());
-    $trail += array(
-      'user/' . $this->admin_user->id() => $this->admin_user->getUsername(),
-    );
-    $this->assertBreadcrumb('user/' . $this->admin_user->id() . '/edit', $trail, $this->admin_user->getUsername());
+    $this->assertBreadcrumb($this->admin_user->urlInfo(), $trail, $this->admin_user->getUsername());
+    $trail += [1 => [
+      'url' => $this->admin_user->urlInfo(),
+      'title' => $this->admin_user->getUsername(),
+    ]];
+    $this->assertBreadcrumb($this->admin_user->urlInfo('edit-form'), $trail, $this->admin_user->getUsername());
 
     // Verify correct breadcrumb and page title when viewing own user account.
     $trail = $home;
-    $this->assertBreadcrumb('user/' . $this->web_user->id(), $trail, $this->web_user->getUsername());
-    $trail += array(
-      'user/' . $this->web_user->id() => $this->web_user->getUsername(),
-    );
-    $this->assertBreadcrumb('user/' . $this->web_user->id() . '/edit', $trail, $this->web_user->getUsername());
+    $this->assertBreadcrumb($this->web_user->urlInfo(), $trail, $this->web_user->getUsername());
+    $trail += [1 => [
+      'url' => $this->web_user->urlInfo(),
+      'title' => $this->web_user->getUsername(),
+    ]];
+    $this->assertBreadcrumb($this->web_user->urlInfo('edit-form'), $trail, $this->web_user->getUsername());
 
     // Create an only slightly privileged user being able to access site reports
     // but not administration pages.
@@ -349,17 +397,20 @@ function testBreadCrumbs() {
     // page title, and that the breadcrumb is just the Home link (because the
     // user is not able to access "Administer".
     $trail = $home;
-    $this->assertBreadcrumb('admin', $trail, t('Access denied'));
+    $this->assertBreadcrumb(Url::fromRoute('system.admin'), $trail, t('Access denied'));
     $this->assertResponse(403);
 
     // Since the 'admin' path is not accessible, we still expect only the Home
     // link.
-    $this->assertBreadcrumb('admin/reports', $trail, t('Reports'));
+    $this->assertBreadcrumb(Url::fromRoute('system.admin_reports'), $trail, t('Reports'));
     $this->assertNoResponse(403);
 
     // Since the Reports page is accessible, that will show.
-    $trail += array('admin/reports' => t('Reports'));
-    $this->assertBreadcrumb('admin/reports/dblog', $trail, t('Recent log messages'));
+    $trail += [1 => [
+      'url' => Url::fromRoute('system.admin_reports'),
+      'title' => t('Reports'),
+    ]];
+    $this->assertBreadcrumb(Url::fromRoute('dblog.overview'), $trail, t('Recent log messages'));
     $this->assertNoResponse(403);
 
     // Ensure that the breadcrumb is safe against XSS.
diff --git a/core/modules/system/src/Tests/Menu/LocalActionTest.php b/core/modules/system/src/Tests/Menu/LocalActionTest.php
index 3c02aad..c049198 100644
--- a/core/modules/system/src/Tests/Menu/LocalActionTest.php
+++ b/core/modules/system/src/Tests/Menu/LocalActionTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Menu;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -27,12 +28,12 @@ class LocalActionTest extends WebTestBase {
   public function testLocalAction() {
     $this->drupalGet('menu-test-local-action');
     // Ensure that both menu and route based actions are shown.
-    $this->assertLocalAction(array(
-      'menu-test-local-action/dynamic-title' => 'My dynamic-title action',
-      'menu-test-local-action/hook_menu' => 'My hook_menu action',
-      'menu-test-local-action/routing' => 'My YAML discovery action',
-      'menu-test-local-action/routing2' => 'Title override',
-    ));
+    $this->assertLocalAction([
+      [Url::fromRoute('menu_test.local_action4'), 'My dynamic-title action'],
+      [Url::fromRoute('menu_test.local_action2'), 'My hook_menu action'],
+      [Url::fromRoute('menu_test.local_action3'), 'My YAML discovery action'],
+      [Url::fromRoute('menu_test.local_action5'), 'Title override'],
+    ]);
   }
 
   /**
@@ -46,9 +47,11 @@ protected function assertLocalAction(array $actions) {
       ':class' => 'button-action',
     ));
     $index = 0;
-    foreach ($actions as $href => $title) {
+    foreach ($actions as $action) {
+      /** @var \Drupal\Core\Url $url */
+      list($url, $title) = $action;
       $this->assertEqual((string) $elements[$index], $title);
-      $this->assertEqual($elements[$index]['href'], _url($href));
+      $this->assertEqual($elements[$index]['href'], $url->toString());
       $index++;
     }
   }
diff --git a/core/modules/system/src/Tests/Menu/LocalTasksTest.php b/core/modules/system/src/Tests/Menu/LocalTasksTest.php
index fc61315..37b44d8 100644
--- a/core/modules/system/src/Tests/Menu/LocalTasksTest.php
+++ b/core/modules/system/src/Tests/Menu/LocalTasksTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Menu;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -34,7 +35,7 @@ protected function assertLocalTasks(array $hrefs, $level = 0) {
     ));
     $this->assertTrue(count($elements), 'Local tasks found.');
     foreach ($hrefs as $index => $element) {
-      $expected = _url($hrefs[$index]);
+      $expected = Url::fromUri('base://' . $hrefs[$index])->toString();
       $method = ($elements[$index]['href'] == $expected ? 'pass' : 'fail');
       $this->{$method}(format_string('Task @number href @value equals @expected.', array(
         '@number' => $index + 1,
diff --git a/core/modules/system/src/Tests/Menu/MenuRouterTest.php b/core/modules/system/src/Tests/Menu/MenuRouterTest.php
index 68617be..0631697 100644
--- a/core/modules/system/src/Tests/Menu/MenuRouterTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuRouterTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Menu;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -62,7 +63,8 @@ public function testMenuIntegration() {
    */
   protected function doTestHookMenuIntegration() {
     // Generate base path with random argument.
-    $base_path = 'foo/' . $this->randomMachineName(8);
+    $machine_name = $this->randomMachineName(8);
+    $base_path = 'foo/' . $machine_name;
     $this->drupalGet($base_path);
     // Confirm correct controller activated.
     $this->assertText('test1');
@@ -70,8 +72,8 @@ protected function doTestHookMenuIntegration() {
     $this->assertLink('Local task A');
     $this->assertLink('Local task B');
     // Confirm correct local task href.
-    $this->assertLinkByHref(_url($base_path));
-    $this->assertLinkByHref(_url($base_path . '/b'));
+    $this->assertLinkByHref(Url::fromRoute('menu_test.router_test1', ['bar' => $machine_name])->toString());
+    $this->assertLinkByHref(Url::fromRoute('menu_test.router_test2', ['bar' => $machine_name])->toString());
   }
 
   /**
diff --git a/core/modules/system/src/Tests/Menu/MenuTestBase.php b/core/modules/system/src/Tests/Menu/MenuTestBase.php
index 2478af3..587fb4d 100644
--- a/core/modules/system/src/Tests/Menu/MenuTestBase.php
+++ b/core/modules/system/src/Tests/Menu/MenuTestBase.php
@@ -34,7 +34,7 @@
    */
   protected function assertBreadcrumb($goto, array $trail, $page_title = NULL, array $tree = array(), $last_active = TRUE) {
     if (isset($goto)) {
-      $this->drupalGet($goto);
+      $this->drupalGet((string) $goto);
     }
     $this->assertBreadcrumbParts($trail);
 
@@ -62,9 +62,12 @@ protected function assertBreadcrumbParts($trail) {
     $pass = TRUE;
     // There may be more than one breadcrumb on the page. If $trail is empty
     // this test would go into an infinite loop, so we need to check that too.
+    $expected_titles = [];
     while ($trail && !empty($parts)) {
-      foreach ($trail as $path => $title) {
-        $url = _url($path);
+      foreach ($trail as $trail_parts) {
+        $title = $trail_parts['title'];
+        $expected_titles[] = $title;
+        $url = $trail_parts['url']->toString();
         $part = array_shift($parts);
         $pass = ($pass && $part['href'] === $url && $part['text'] === String::checkPlain($title));
       }
@@ -73,7 +76,7 @@ protected function assertBreadcrumbParts($trail) {
     $pass = ($pass && empty($parts));
 
     $this->assertTrue($pass, format_string('Breadcrumb %parts found on @path.', array(
-      '%parts' => implode(' » ', $trail),
+      '%parts' => implode(' » ', $expected_titles),
       '@path' => $this->getUrl(),
     )));
   }
@@ -91,19 +94,22 @@ protected function assertBreadcrumbParts($trail) {
    */
   protected function assertMenuActiveTrail($tree, $last_active) {
     end($tree);
-    $active_link_path = key($tree);
-    $active_link_title = array_pop($tree);
+    $active_link_url = current($tree)['url']->toString();
+    $active_link_title = current($tree)['title'];
+    array_pop($tree);
     $xpath = '';
+    $expected_titles = [];
     if ($tree) {
       $i = 0;
-      foreach ($tree as $link_path => $link_title) {
+      foreach ($tree as $tree_element) {
         $part_xpath = (!$i ? '//' : '/following-sibling::ul/descendant::');
         $part_xpath .= 'li[contains(@class, :class)]/a[contains(@href, :href) and contains(text(), :title)]';
         $part_args = array(
           ':class' => 'active-trail',
-          ':href' => _url($link_path),
-          ':title' => $link_title,
+          ':href' => $tree_element['url']->toString(),
+          ':title' => $tree_element['title'],
         );
+        $expected_titles[] = $tree_element['title'];
         $xpath .= $this->buildXPathQuery($part_xpath, $part_args);
         $i++;
       }
@@ -121,13 +127,13 @@ protected function assertMenuActiveTrail($tree, $last_active) {
     $args = array(
       ':class-trail' => 'active-trail',
       ':class-active' => 'active',
-      ':href' => _url($active_link_path),
+      ':href' => $active_link_url,
       ':title' => $active_link_title,
     );
     $elements = $this->xpath($xpath, $args);
     $this->assertTrue(!empty($elements), format_string('Active link %title was found in menu tree, including active trail links %tree.', array(
       '%title' => $active_link_title,
-      '%tree' => implode(' » ', $tree),
+      '%tree' => implode(' » ', $expected_titles),
     )));
   }
 
diff --git a/core/modules/system/src/Tests/Menu/MenuTranslateTest.php b/core/modules/system/src/Tests/Menu/MenuTranslateTest.php
index ea4b302..e316c95 100644
--- a/core/modules/system/src/Tests/Menu/MenuTranslateTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuTranslateTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Menu;
 
+use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -40,7 +41,7 @@ public function testMenuTranslate() {
     $this->assertResponse(403);
     $elements = $this->xpath('//ul[@class=:class]/li/a[@href=:href]', array(
       ':class' => 'tabs primary',
-      ':href' => _url('foo/asdf'),
+      ':href' => Url::fromRoute('menu_test.router_test1', ['bar' => 'asdf'])->toString(),
     ));
     $this->assertTrue(empty($elements), 'No tab linking to foo/asdf found');
     $this->assertNoLinkByHref('foo/asdf/b');
diff --git a/core/modules/system/src/Tests/Theme/FunctionsTest.php b/core/modules/system/src/Tests/Theme/FunctionsTest.php
index c1ad1c7..abdcff6 100644
--- a/core/modules/system/src/Tests/Theme/FunctionsTest.php
+++ b/core/modules/system/src/Tests/Theme/FunctionsTest.php
@@ -217,7 +217,7 @@ function testLinks() {
     $expected_links .= '<ul id="somelinks">';
     $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base://a/link')->toString() . '">' . String::checkPlain('A <link>') . '</a></li>';
     $expected_links .= '<li class="plain-text">' . String::checkPlain('Plain "text"') . '</li>';
-    $expected_links .= '<li class="front-page"><a href="' . _url('<front>') . '">' . String::checkPlain('Front page') . '</a></li>';
+    $expected_links .= '<li class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '">' . String::checkPlain('Front page') . '</a></li>';
     $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . String::checkPlain('Test route') . '</a></li>';
     $query = array('key' => 'value');
     $expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . String::checkPlain('Query test route') . '</a></li>';
@@ -256,7 +256,7 @@ function testLinks() {
     $expected_links .= '<ul id="somelinks">';
     $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base://a/link')->toString() . '">' . String::checkPlain('A <link>') . '</a></li>';
     $expected_links .= '<li class="plain-text"><span class="a/class">' . String::checkPlain('Plain "text"') . '</span></li>';
-    $expected_links .= '<li class="front-page"><a href="' . _url('<front>') . '">' . String::checkPlain('Front page') . '</a></li>';
+    $expected_links .= '<li class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '">' . String::checkPlain('Front page') . '</a></li>';
     $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . String::checkPlain('Test route') . '</a></li>';
     $query = array('key' => 'value');
     $expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . String::checkPlain('Query test route') . '</a></li>';
@@ -271,7 +271,7 @@ function testLinks() {
     $expected_links .= '<ul id="somelinks">';
     $expected_links .= '<li class="a-link"><a href="' . Url::fromUri('base://a/link')->toString() . '">' . String::checkPlain('A <link>') . '</a></li>';
     $expected_links .= '<li class="plain-text"><span class="a/class">' . String::checkPlain('Plain "text"') . '</span></li>';
-    $expected_links .= '<li data-drupal-link-system-path="&lt;front&gt;" class="front-page"><a href="' . _url('<front>') . '" data-drupal-link-system-path="&lt;front&gt;">' . String::checkPlain('Front page') . '</a></li>';
+    $expected_links .= '<li data-drupal-link-system-path="&lt;front&gt;" class="front-page"><a href="' . Url::fromRoute('<front>')->toString() . '" data-drupal-link-system-path="&lt;front&gt;">' . String::checkPlain('Front page') . '</a></li>';
     $expected_links .= '<li data-drupal-link-system-path="router_test/test1" class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '" data-drupal-link-system-path="router_test/test1">' . String::checkPlain('Test route') . '</a></li>';
     $query = array('key' => 'value');
     $encoded_query = String::checkPlain(Json::encode($query));
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 8f2a8ab..a41b080 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -457,7 +457,7 @@ function system_authorized_run($callback, $file, $arguments = array(), $page_tit
 function system_authorized_batch_process() {
   $finish_url = system_authorized_get_url();
   $process_url = system_authorized_batch_processing_url();
-  return batch_process($finish_url->toString(), $process_url->toString());
+  return batch_process($finish_url->toString(), $process_url);
 }
 
 /**
@@ -691,11 +691,11 @@ function system_user_presave(UserInterface $account) {
 /**
  * Implements hook_user_login().
  */
-function system_user_login($account) {
+function system_user_login(UserInterface $account) {
   $config = \Drupal::config('system.date');
   // If the user has a NULL time zone, notify them to set a time zone.
   if (!$account->getTimezone() && $config->get('timezone.user.configurable') && $config->get('timezone.user.warn')) {
-    drupal_set_message(t('Configure your <a href="@user-edit">account time zone setting</a>.', array('@user-edit' => _url("user/$account->id()/edit", array('query' => drupal_get_destination(), 'fragment' => 'edit-timezone')))));
+    drupal_set_message(t('Configure your <a href="@user-edit">account time zone setting</a>.', array('@user-edit' => $account->url('edit-form', array('query' => drupal_get_destination(), 'fragment' => 'edit-timezone')))));
   }
 }
 
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index 7fa29a9..fc4f301 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -41,7 +41,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
- *     "canonical" = "entity.entity_test_mulrev.edit_form",
+ *     "canonical" = "entity.entity_test_mulrev.canonical",
  *     "delete-form" = "entity.entity_test_mulrev.delete_form",
  *     "edit-form" = "entity.entity_test_mulrev.edit_form"
  *   }
diff --git a/core/modules/system/tests/modules/session_test/session_test.module b/core/modules/system/tests/modules/session_test/session_test.module
index 0fb2ae4..53e05c8 100644
--- a/core/modules/system/tests/modules/session_test/session_test.module
+++ b/core/modules/system/tests/modules/session_test/session_test.module
@@ -1,9 +1,10 @@
 <?php
+use Drupal\user\UserInterface;
 
 /**
  * Implements hook_user_login().
  */
-function session_test_user_login($account) {
+function session_test_user_login(UserInterface $account) {
   if ($account->getUsername() == 'session_test_user') {
     // Exit so we can verify that the session was regenerated
     // before hook_user_login() was called.
diff --git a/core/modules/update/src/Tests/UpdateCoreTest.php b/core/modules/update/src/Tests/UpdateCoreTest.php
index 9b8f058..c2747fc 100644
--- a/core/modules/update/src/Tests/UpdateCoreTest.php
+++ b/core/modules/update/src/Tests/UpdateCoreTest.php
@@ -190,7 +190,7 @@ function testDatestampMismatch() {
   function testModulePageRunCron() {
     $this->setSystemInfo('8.0.0');
     $this->config('update.settings')
-      ->set('fetch.url', _url('update-test', array('absolute' => TRUE)))
+      ->set('fetch.url', Url::fromUri('base://update-test', array('absolute' => TRUE))->toString())
       ->save();
     $this->config('update_test.settings')
       ->set('xml_map', array('drupal' => '0.0'))
@@ -208,7 +208,7 @@ function testModulePageUpToDate() {
     $this->setSystemInfo('8.0.0');
     // Instead of using refreshUpdateStatus(), set these manually.
     $this->config('update.settings')
-      ->set('fetch.url', _url('update-test', array('absolute' => TRUE)))
+      ->set('fetch.url', Url::fromUri('base://update-test', array('absolute' => TRUE))->toString())
       ->save();
     $this->config('update_test.settings')
       ->set('xml_map', array('drupal' => '0.0'))
@@ -229,7 +229,7 @@ function testModulePageRegularUpdate() {
     $this->setSystemInfo('8.0.0');
     // Instead of using refreshUpdateStatus(), set these manually.
     $this->config('update.settings')
-      ->set('fetch.url', _url('update-test', array('absolute' => TRUE)))
+      ->set('fetch.url', Url::fromUri('base://update-test', array('absolute' => TRUE))->toString())
       ->save();
     $this->config('update_test.settings')
       ->set('xml_map', array('drupal' => '0.1'))
@@ -250,7 +250,7 @@ function testModulePageSecurityUpdate() {
     $this->setSystemInfo('8.0.0');
     // Instead of using refreshUpdateStatus(), set these manually.
     $this->config('update.settings')
-      ->set('fetch.url', _url('update-test', array('absolute' => TRUE)))
+      ->set('fetch.url', Url::fromUri('base://update-test', array('absolute' => TRUE))->toString())
       ->save();
     $this->config('update_test.settings')
       ->set('xml_map', array('drupal' => '0.2-sec'))
@@ -325,7 +325,7 @@ function testLanguageModuleUpdate() {
     $this->setSystemInfo('8.0.0');
     // Instead of using refreshUpdateStatus(), set these manually.
     $this->config('update.settings')
-      ->set('fetch.url', _url('update-test', array('absolute' => TRUE)))
+      ->set('fetch.url', Url::fromUri('base://update-test', array('absolute' => TRUE))->toString())
       ->save();
     $this->config('update_test.settings')
       ->set('xml_map', array('drupal' => '0.1'))
diff --git a/core/modules/update/src/Tests/UpdateTestBase.php b/core/modules/update/src/Tests/UpdateTestBase.php
index 1b1c9f6..5df322a 100644
--- a/core/modules/update/src/Tests/UpdateTestBase.php
+++ b/core/modules/update/src/Tests/UpdateTestBase.php
@@ -44,7 +44,7 @@
   protected function refreshUpdateStatus($xml_map, $url = 'update-test') {
     // Tell the Update Manager module to fetch from the URL provided by
     // update_test module.
-    $this->config('update.settings')->set('fetch.url', _url($url, array('absolute' => TRUE)))->save();
+    $this->config('update.settings')->set('fetch.url', Url::fromUri('base://' . $url, array('absolute' => TRUE))->toString())->save();
     // Save the map for update_test_mock_page() to use.
     $this->config('update_test.settings')->set('xml_map', $xml_map)->save();
     // Manually check the update status.
diff --git a/core/modules/update/src/Tests/UpdateUploadTest.php b/core/modules/update/src/Tests/UpdateUploadTest.php
index 7a916fe..037a0dc 100644
--- a/core/modules/update/src/Tests/UpdateUploadTest.php
+++ b/core/modules/update/src/Tests/UpdateUploadTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\update\Tests;
 
+use Drupal\Core\Url;
+
 /**
  * Tests the Update Manager module's upload and extraction functionality.
  *
@@ -80,7 +82,7 @@ function testUpdateManagerCoreSecurityUpdateMessages() {
       ->set('xml_map', array('drupal' => '0.2-sec'))
       ->save();
     $this->config('update.settings')
-      ->set('fetch.url', _url('update-test', array('absolute' => TRUE)))
+      ->set('fetch.url', Url::fromUri('base://update-test', array('absolute' => TRUE))->toString())
       ->save();
     // Initialize the update status.
     $this->drupalGet('admin/reports/updates');
diff --git a/core/modules/update/tests/modules/update_test/update_test.routing.yml b/core/modules/update/tests/modules/update_test/update_test.routing.yml
index 708d42f..5dd8d4c 100644
--- a/core/modules/update/tests/modules/update_test/update_test.routing.yml
+++ b/core/modules/update/tests/modules/update_test/update_test.routing.yml
@@ -11,5 +11,6 @@ update_test.update_test:
     _title: 'Update test'
     _controller: '\Drupal\update_test\Controller\UpdateTestController::updateTest'
     version: NULL
+    project_name: NULL
   requirements:
     _access: 'TRUE'
diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php
index 928a56a..4a06f1e 100644
--- a/core/modules/user/user.api.php
+++ b/core/modules/user/user.api.php
@@ -1,6 +1,7 @@
 <?php
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\user\UserInterface;
 
 /**
  * @file
@@ -132,7 +133,7 @@ function hook_user_format_name_alter(&$name, $account) {
  * @param $account
  *   The user object on which the operation was just performed.
  */
-function hook_user_login($account) {
+function hook_user_login(UserInterface $account) {
   $config = \Drupal::config('system.date');
   // If the user has a NULL time zone, notify them to set a time zone.
   if (!$account->getTimezone() && $config->get('timezone.user.configurable') && $config->get('timezone.user.warn')) {
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index a251fd7..97fdee7 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -622,7 +622,7 @@ function user_login_finalize(UserInterface $account) {
 /**
  * Implements hook_user_login().
  */
-function user_user_login($account) {
+function user_user_login(UserInterface $account) {
   // Reset static cache of default variables in template_preprocess() to reflect
   // the new user.
   drupal_static_reset('template_preprocess');
diff --git a/core/modules/views/src/Plugin/views/display/DisplayRouterInterface.php b/core/modules/views/src/Plugin/views/display/DisplayRouterInterface.php
index 7c30517..92ff598 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayRouterInterface.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayRouterInterface.php
@@ -38,4 +38,11 @@ public function collectRoutes(RouteCollection $collection);
    */
   public function alterRoutes(RouteCollection $collection);
 
+  /**
+   * Generates an URL to this display.
+   *
+   * @return \Drupal\Core\Url
+   */
+  public function getUrlInfo();
+
 }
diff --git a/core/modules/views/src/Plugin/views/display/PathPluginBase.php b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
index fd93c3c..4503d0a 100644
--- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php
@@ -486,5 +486,15 @@ public function validate() {
     return $errors;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getUrlInfo() {
+    if (strpos($this->getOption('path'), '%') !== FALSE) {
+      throw new \InvalidArgumentException('No placeholders supported yet.');
+    }
+
+    return Url::fromRoute($this->getRoute($this->view->storage->id(), $this->display['id']));
+  }
 
 }
diff --git a/core/modules/views/src/Tests/GlossaryTest.php b/core/modules/views/src/Tests/GlossaryTest.php
index 61fb6a3..1d69b77 100644
--- a/core/modules/views/src/Tests/GlossaryTest.php
+++ b/core/modules/views/src/Tests/GlossaryTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Tests;
 
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Url;
 use Drupal\views\Views;
 
 /**
@@ -66,7 +67,7 @@ public function testGlossaryView() {
     $this->assertResponse(200);
 
     foreach ($nodes_per_char as $char => $count) {
-      $href = _url('glossary/' . $char);
+      $href = Url::fromRoute('view.glossary.page_1', ['arg_0' => $char])->toString();
       $label = Unicode::strtoupper($char);
       // Get the summary link for a certain character. Filter by label and href
       // to ensure that both of them are correct.
diff --git a/core/modules/views/src/Tests/Plugin/ExposedFormTest.php b/core/modules/views/src/Tests/Plugin/ExposedFormTest.php
index c6bc335..895d9b5 100644
--- a/core/modules/views/src/Tests/Plugin/ExposedFormTest.php
+++ b/core/modules/views/src/Tests/Plugin/ExposedFormTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Tests\Plugin;
 
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Url;
 use Drupal\views\Tests\ViewTestBase;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Views;
@@ -135,7 +136,8 @@ public function testExposedFormRender() {
 
     $this->assertFieldByXpath('//form/@id', $this->getExpectedExposedFormId($view), 'Expected form ID found.');
 
-    $expected_action = _url($view->display_handler->getUrl());
+    $view->setDisplay('page_1');
+    $expected_action = $view->display_handler->getUrlInfo()->toString();
     $this->assertFieldByXPath('//form/@action', $expected_action, 'The expected value for the action attribute was found.');
   }
 
diff --git a/core/modules/views/src/Tests/TokenReplaceTest.php b/core/modules/views/src/Tests/TokenReplaceTest.php
index 13ae1cd..f3f0028 100644
--- a/core/modules/views/src/Tests/TokenReplaceTest.php
+++ b/core/modules/views/src/Tests/TokenReplaceTest.php
@@ -44,7 +44,7 @@ function testTokenReplacement() {
       '[view:description]' => 'Test view to token replacement tests.',
       '[view:id]' => 'test_tokens',
       '[view:title]' => 'Test token page',
-      '[view:url]' => _url('test_tokens', array('absolute' => TRUE)),
+      '[view:url]' => $view->getUrlInfo('page_1')->setAbsolute(TRUE)->toString(),
       '[view:total-rows]' => (string) $view->total_rows,
       '[view:base-table]' => 'views_test_data',
       '[view:base-field]' => 'id',
diff --git a/core/modules/views/src/Tests/Wizard/BasicTest.php b/core/modules/views/src/Tests/Wizard/BasicTest.php
index c85c130..8bb84ab 100644
--- a/core/modules/views/src/Tests/Wizard/BasicTest.php
+++ b/core/modules/views/src/Tests/Wizard/BasicTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\String;
+use Drupal\Core\Url;
 use Drupal\views\Views;
 
 /**
@@ -74,8 +75,8 @@ function testViewsWizardAndListing() {
     $this->assertText($node2->label());
 
     // Check if we have the feed.
-    $this->assertLinkByHref(_url($view2['page[feed_properties][path]']));
-    $elements = $this->cssSelect('link[href="' . _url($view2['page[feed_properties][path]'], ['absolute' => TRUE]) . '"]');
+    $this->assertLinkByHref(Url::fromRoute('view.' . $view2['id'] . '.feed_1')->toString());
+    $elements = $this->cssSelect('link[href="' . Url::fromRoute('view.' . $view2['id'] . '.feed_1', [], ['absolute' => TRUE])->toString() . '"]');
     $this->assertEqual(count($elements), 1, 'Feed found.');
     $this->drupalGet($view2['page[feed_properties][path]']);
     $this->assertRaw('<rss version="2.0"');
@@ -90,7 +91,7 @@ function testViewsWizardAndListing() {
     $this->drupalGet('admin/structure/views');
     $this->assertText($view2['label']);
     $this->assertText($view2['description']);
-    $this->assertLinkByHref(_url($view2['page[path]']));
+    $this->assertLinkByHref(Url::fromRoute('view.' . $view2['id'] . '.page_1')->toString());
 
     // The view should not have a REST export display.
     $this->assertNoText('REST export', 'If only the page option was enabled in the wizard, the resulting view does not have a REST export display.');
@@ -125,7 +126,7 @@ function testViewsWizardAndListing() {
     $this->drupalGet('admin/structure/views');
     $this->assertText($view3['label']);
     $this->assertText($view3['description']);
-    $this->assertLinkByHref(_url($view3['page[path]']));
+    $this->assertLinkByHref(Url::fromRoute('view.' . $view3['id'] . '.page_1')->toString());
 
     // The view should not have a REST export display.
     $this->assertNoText('REST export', 'If only the page and block options were enabled in the wizard, the resulting view does not have a REST export display.');
diff --git a/core/modules/views/src/Tests/Wizard/MenuTest.php b/core/modules/views/src/Tests/Wizard/MenuTest.php
index 8c4f7b9..63a325c 100644
--- a/core/modules/views/src/Tests/Wizard/MenuTest.php
+++ b/core/modules/views/src/Tests/Wizard/MenuTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Tests\Wizard;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Url;
 
 /**
  * Tests the ability of the views wizard to put views in a menu.
@@ -40,7 +41,7 @@ function testMenus() {
     $this->drupalGet('');
     $this->assertResponse(200);
     $this->assertLink($view['page[link_properties][title]']);
-    $this->assertLinkByHref(_url($view['page[path]']));
+    $this->assertLinkByHref(Url::fromUri('base://' . $view['page[path]'])->toString());
 
     // Make sure the link is associated with the main menu.
     /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php
index 0ca31da..3f27d00 100644
--- a/core/modules/views/src/ViewExecutable.php
+++ b/core/modules/views/src/ViewExecutable.php
@@ -11,6 +11,7 @@
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\views\Plugin\views\display\DisplayRouterInterface;
 use Drupal\views\Plugin\views\query\QueryPluginBase;
 use Drupal\views\ViewStorageInterface;
 use Drupal\Component\Utility\Tags;
@@ -1758,6 +1759,26 @@ public function getUrl($args = NULL, $path = NULL) {
   }
 
   /**
+   * Gets the Url object associated with the display handler.
+   *
+   * @param string $display_id
+   *   (Optional) The display id. ( Used only to detail an exception. )
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the display plugin does not have a URL to return.
+   *
+   * @return \Drupal\Core\Url
+   *   The display handlers URL object.
+   */
+  public function getUrlInfo($display_id = '') {
+    $this->initDisplay();
+    if (!$this->display_handler instanceof DisplayRouterInterface) {
+      throw new \InvalidArgumentException(String::format('You cannot get generate a URL for the display @display_id', ['@display_id' => $display_id]));
+    }
+    return $this->display_handler->getUrlInfo();
+  }
+
+  /**
    * Get the base path used for this view.
    */
   public function getPath() {
diff --git a/core/modules/views_ui/src/Tests/DefaultViewsTest.php b/core/modules/views_ui/src/Tests/DefaultViewsTest.php
index 9c15ba8..68e298c 100644
--- a/core/modules/views_ui/src/Tests/DefaultViewsTest.php
+++ b/core/modules/views_ui/src/Tests/DefaultViewsTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\views_ui\Tests;
 
+use Drupal\Core\Url;
+
 /**
  * Tests enabling, disabling, and reverting default views via the listing page.
  *
@@ -168,7 +170,7 @@ public function testPathDestination() {
 
     // Check that a dynamic path is shown as text.
     $this->assertRaw('test_route_with_suffix/%/suffix');
-    $this->assertNoLinkByHref(_url('test_route_with_suffix/%/suffix'));
+    $this->assertNoLinkByHref(Url::fromUri('base://test_route_with_suffix/%/suffix')->toString());
   }
 
   /**
