diff --git a/core/core.services.yml b/core/core.services.yml
index 2ab84ee..8b4eed0 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -264,7 +264,7 @@ services:
       - [setFinalMatcher, ['@router.matcher.final_matcher']]
   url_generator:
     class: Drupal\Core\Routing\UrlGenerator
-    arguments: ['@router.route_provider', '@path_processor_manager', '@config.factory', '@settings']
+    arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@settings']
     calls:
       - [setRequest, ['@?request']]
       - [setContext, ['@?router.request_context']]
@@ -387,7 +387,7 @@ services:
     arguments: ['@http_kernel', '@controller_resolver', '@string_translation', '@title_resolver']
   controller.dialog:
     class: Drupal\Core\Controller\DialogController
-    arguments: ['@http_kernel']
+    arguments: ['@http_kernel', '@title_resolver']
   router_listener:
     class: Symfony\Component\HttpKernel\EventListener\RouterListener
     tags:
@@ -449,6 +449,11 @@ services:
     arguments: ['@controller_resolver']
     tags:
       - { name: access_check }
+  access_check.csrf:
+    class: Drupal\Core\Access\CsrfAccessCheck
+    tags:
+      - { name: access_check }
+    arguments: ['@csrf_token']
   maintenance_mode_subscriber:
     class: Drupal\Core\EventSubscriber\MaintenanceModeSubscriber
     tags:
@@ -507,6 +512,8 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: [['@exception_controller', execute]]
+  route_processor_manager:
+    class: Drupal\Core\RouteProcessor\RouteProcessorManager
   path_processor_manager:
     class: Drupal\Core\PathProcessor\PathProcessorManager
   path_processor_decode:
@@ -525,6 +532,11 @@ services:
       - { name: path_processor_inbound, priority: 100 }
       - { name: path_processor_outbound, priority: 300 }
     arguments: ['@path.alias_manager']
+  route_processer_csrf:
+    class: Drupal\Core\Access\RouteProcessorCsrf
+    tags:
+      - { name: route_processor_outbound }
+    arguments: ['@csrf_token']
   transliteration:
     class: Drupal\Core\Transliteration\PHPTransliteration
   flood:
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 417d7ef..a8a30b6 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -150,11 +150,6 @@
 const MENU_IS_LOCAL_TASK = 0x0080;
 
 /**
- * Internal menu flag -- menu item is a local action.
- */
-const MENU_IS_LOCAL_ACTION = 0x0100;
-
-/**
  * @} End of "defgroup menu_flags".
  */
 
@@ -212,20 +207,12 @@
 define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT | MENU_VISIBLE_IN_BREADCRUMB);
 
 /**
- * Menu type -- An action specific to the parent, usually rendered as a link.
- *
- * Local actions are menu items that describe actions on the parent item such
- * as adding a new user, taxonomy term, etc.
- */
-define('MENU_LOCAL_ACTION', MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB);
-
-/**
  * Menu type -- A task specific to the parent, which is never rendered.
  *
  * Sibling local tasks are not rendered themselves, but affect the active
  * trail and need their sibling tasks rendered as tabs.
  */
-define('MENU_SIBLING_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB);
+define('MENU_SIBLING_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_VISIBLE_IN_BREADCRUMB);
 
 /**
  * @} End of "defgroup menu_item_types".
@@ -2117,27 +2104,12 @@ function _menu_get_legacy_tasks($router_item, &$data, &$root_path) {
           $tab_count++;
         }
         else {
-          // Actions can be normal items too, so bitmask with
-          // MENU_IS_LOCAL_ACTION before checking.
-          if (($item['type'] & MENU_IS_LOCAL_ACTION) == MENU_IS_LOCAL_ACTION) {
-            // The item is an action, display it as such.
-            $key = isset($link['route_name']) ? $link['route_name'] : $link['href'];
-            $actions_current[$key] = array(
-              '#theme' => 'menu_local_action',
-              '#link' => $link,
-              '#weight' => isset($link['weight']) ? $link['weight'] : NULL,
-            );
-            $action_count++;
-          }
-          else {
-            // Otherwise, it's a normal tab.
-            $tabs_current[$link['href']] = array(
-              '#theme' => 'menu_local_task',
-              '#link' => $link,
-              '#weight' => isset($link['weight']) ? $link['weight'] : NULL,
-            );
-            $tab_count++;
-          }
+          $tabs_current[$link['href']] = array(
+            '#theme' => 'menu_local_task',
+            '#link' => $link,
+            '#weight' => isset($link['weight']) ? $link['weight'] : NULL,
+          );
+          $tab_count++;
         }
       }
     }
@@ -2158,10 +2130,6 @@ function _menu_get_legacy_tasks($router_item, &$data, &$root_path) {
     $next_parent = '';
     $count = 0;
     foreach ($children[$parent] as $item) {
-      // Skip local actions.
-      if ($item['type'] & MENU_IS_LOCAL_ACTION) {
-        continue;
-      }
       if ($item['access']) {
         $count++;
         $link = $item;
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php b/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php
index fc07024..8477f1e 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/CachedDiscoveryInterface.php
@@ -14,6 +14,10 @@
 
   /**
    * Clears static and persistent plugin definition caches.
+   *
+   * Don't resort to calling \Drupal::cache()->delete() and friends to make
+   * Drupal detect new or updated plugin definitions. Always use this method on
+   * the appropriate plugin type's plugin manager!
    */
   public function clearCachedDefinitions();
 
diff --git a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
new file mode 100644
index 0000000..d6ba3a8
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Access\CsrfAccessCheck.
+ */
+
+namespace Drupal\Core\Access;
+
+use Drupal\Core\Access\CsrfTokenGenerator;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Allows access to routes to be controlled by a '_csrf_token' parameter.
+ *
+ * To use this check, add a "token" GET parameter to URLs of which the value is
+ * a token generated by \Drupal::csrfToken()->get() using the same value as the
+ * "_csrf_token" parameter in the route.
+ */
+class CsrfAccessCheck implements StaticAccessCheckInterface {
+
+  /**
+   * The CSRF token generator.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator
+   */
+  protected $csrfToken;
+
+  /**
+   * Constructs a CsrfAccessCheck object.
+   *
+   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
+   *   The CSRF token generator.
+   */
+  function __construct(CsrfTokenGenerator $csrf_token) {
+    $this->csrfToken = $csrf_token;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_csrf_token');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request, AccountInterface $account) {
+    // If this is the controller request, check CSRF access as normal.
+    if ($request->attributes->get('_controller_request')) {
+      return $this->csrfToken->validate($request->query->get('token'), $route->getRequirement('_csrf_token')) ? static::ALLOW : static::KILL;
+    }
+
+    // Otherwise, this could be another requested access check that we don't
+    // want to check CSRF tokens on.
+    $conjunction = $route->getOption('_access_mode') ?: 'ANY';
+    // Return ALLOW if all access checks are needed.
+    if ($conjunction == 'ALL') {
+      return static::ALLOW;
+    }
+    // Return DENY otherwise, as another access checker should grant access
+    // for the route.
+    else {
+      return static::DENY;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
new file mode 100644
index 0000000..0fb075c
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Access\RouteProcessorCsrf.
+ */
+
+namespace Drupal\Core\Access;
+
+use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
+use Drupal\Core\Access\CsrfTokenGenerator;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Processes the inbound path by resolving it to the front page if empty.
+ */
+class RouteProcessorCsrf implements OutboundRouteProcessorInterface {
+
+  /**
+   * The CSRF token generator.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator
+   */
+  protected $csrfToken;
+
+  /**
+   * Constructs a RouteProcessorCsrf object.
+   *
+   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
+   *   The CSRF token generator.
+   */
+  function __construct(CsrfTokenGenerator $csrf_token) {
+    $this->csrfToken = $csrf_token;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processOutbound(Route $route, array &$parameters) {
+    if ($route->hasRequirement('_csrf_token')) {
+      // Adding this to the parameters means it will get merged into the query
+      // string when the route is compiled.
+      $parameters['token'] = $this->csrfToken->get($route->getRequirement('_csrf_token'));
+    }
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php
index 83366b1..59dbad3 100644
--- a/core/lib/Drupal/Core/Config/Config.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -71,7 +71,7 @@ class Config {
   protected $context;
 
   /**
-   * Whether the config object has already been loaded.
+   * Whether the configuration object has already been loaded.
    *
    * @var bool
    */
@@ -139,6 +139,9 @@ public function getName() {
   /**
    * Sets the name of this configuration object.
    *
+   * @param string $name
+   *  The name of the configuration object.
+   *
    * @return \Drupal\Core\Config\Config
    *   The configuration object.
    */
@@ -150,6 +153,9 @@ public function setName($name) {
   /**
    * Validates the configuration object name.
    *
+   * @param string $name
+   *  The name of the configuration object.
+   *
    * @throws \Drupal\Core\Config\ConfigNameException
    *
    * @see Config::MAX_NAME_LENGTH
@@ -182,7 +188,7 @@ public static function validateName($name) {
    * Returns whether this configuration object is new.
    *
    * @return bool
-   *   TRUE if this config object does not exist in storage.
+   *   TRUE if this configuration object does not exist in storage.
    */
   public function isNew() {
     if (!$this->isLoaded) {
@@ -192,11 +198,11 @@ public function isNew() {
   }
 
   /**
-   * Gets data from this config object.
+   * Gets data from this configuration object.
    *
    * @param string $key
    *   A string that maps to a key within the configuration data.
-   *   For instance in the following configuation array:
+   *   For instance in the following configuration array:
    *   @code
    *   array(
    *     'foo' => array(
@@ -317,11 +323,11 @@ protected function resetOverriddenData() {
   }
 
   /**
-   * Sets value in this config object.
+   * Sets a value in this configuration object.
    *
    * @param string $key
-   *   Identifier to store value in config.
-   * @param string $value
+   *   Identifier to store value in configuration.
+   * @param mixed $value
    *   Value to associate with identifier.
    *
    * @return \Drupal\Core\Config\Config
@@ -346,7 +352,7 @@ public function set($key, $value) {
   }
 
   /**
-   * Unsets value in this config object.
+   * Unsets a value in this configuration object.
    *
    * @param string $key
    *   Name of the key whose value should be unset.
@@ -426,7 +432,7 @@ public function delete() {
   }
 
   /**
-   * Retrieve the storage used to load and save this configuration object.
+   * Retrieves the storage used to load and save this configuration object.
    *
    * @return \Drupal\Core\Config\StorageInterface
    *   The configuration storage object.
@@ -436,7 +442,10 @@ public function getStorage() {
   }
 
   /**
-   * Dispatch a config event.
+   * Dispatches a configuration event.
+   *
+   * @param string $config_event_name
+   *   The configuration event name.
    */
   protected function notify($config_event_name) {
     $this->context->notify($config_event_name, $this);
@@ -455,7 +464,7 @@ public function merge(array $data_to_merge) {
     if (!$this->isLoaded) {
       $this->load();
     }
-    // Preserve integer keys so that config keys are not changed.
+    // Preserve integer keys so that configuration keys are not changed.
     $this->replaceData(NestedArray::mergeDeepArray(array($this->data, $data_to_merge), TRUE));
     return $this;
   }
diff --git a/core/lib/Drupal/Core/Controller/ControllerBase.php b/core/lib/Drupal/Core/Controller/ControllerBase.php
index eec090e..618bfee 100644
--- a/core/lib/Drupal/Core/Controller/ControllerBase.php
+++ b/core/lib/Drupal/Core/Controller/ControllerBase.php
@@ -273,7 +273,7 @@ protected function container() {
   }
 
   /**
-   * Returns a redirect response object for the specified
+   * Returns a redirect response object for the specified route.
    *
    * @param string $route_name
    *   The name of the route to which to redirect.
@@ -281,6 +281,7 @@ protected function container() {
    *   Parameters for the route.
    * @param int $status
    *   The HTTP redirect status code for the redirect. The default is 302 Found.
+   *
    * @return \Symfony\Component\HttpFoundation\RedirectResponse
    *   A redirect response object that may be returned by the controller.
    */
diff --git a/core/lib/Drupal/Core/Controller/DialogController.php b/core/lib/Drupal/Core/Controller/DialogController.php
index cde2876..7bb34ff 100644
--- a/core/lib/Drupal/Core/Controller/DialogController.php
+++ b/core/lib/Drupal/Core/Controller/DialogController.php
@@ -26,13 +26,23 @@ class DialogController {
   protected $httpKernel;
 
   /**
+   * The title resolver.
+   *
+   * @var \Drupal\Core\Controller\TitleResolver
+   */
+  protected $titleResolver;
+
+  /**
    * Constructs a new DialogController.
    *
    * @param \Symfony\Component\HttpKernel\HttpKernelInterface $kernel
    *   The kernel.
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver.
    */
-  public function __construct(HttpKernelInterface $kernel) {
+  public function __construct(HttpKernelInterface $kernel, TitleResolverInterface $title_resolver) {
     $this->httpKernel = $kernel;
+    $this->titleResolver = $title_resolver;
   }
 
   /**
@@ -94,9 +104,11 @@ public function dialog(Request $request, $modal = FALSE) {
     $subrequest = $this->forward($request);
     if ($subrequest->isOk()) {
       $content = $subrequest->getContent();
-      // @todo Remove use of drupal_get_title() when
-      //  http://drupal.org/node/1871596 is in.
-      $title = drupal_get_title();
+      if (!$title = $this->titleResolver->getTitle($request, $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT))) {
+        // @todo Remove use of drupal_get_title() when
+        //  http://drupal.org/node/1871596 is in.
+        $title = drupal_get_title();
+      }
       $response = new AjaxResponse();
       // Fetch any modal options passed in from data-dialog-options.
       if (!($options = $request->request->get('dialogOptions'))) {
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index 9cd8f5a..8a525c9 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -14,6 +14,7 @@
 use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterRouteProcessorsPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass;
@@ -63,6 +64,7 @@ public function register(ContainerBuilder $container) {
     $container->addCompilerPass(new RegisterServicesForDestructionPass());
     // Add the compiler pass that will process the tagged services.
     $container->addCompilerPass(new RegisterPathProcessorsPass());
+    $container->addCompilerPass(new RegisterRouteProcessorsPass());
     $container->addCompilerPass(new ListCacheBinsPass());
     // Add the compiler pass for appending string translators.
     $container->addCompilerPass(new RegisterStringTranslatorsPass());
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php
new file mode 100644
index 0000000..dd95869
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterRouteProcessorsPass.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\DependencyInjection\Compiler\RegisterRouteProcessorsPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Adds services to the route_processor_manager service.
+ */
+class RegisterRouteProcessorsPass implements CompilerPassInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process(ContainerBuilder $container) {
+    if (!$container->hasDefinition('route_processor_manager')) {
+      return;
+    }
+    $manager = $container->getDefinition('route_processor_manager');
+    // Add outbound route processors.
+    foreach ($container->findTaggedServiceIds('route_processor_outbound') as $id => $attributes) {
+      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
+      $manager->addMethodCall('addOutbound', array(new Reference($id), $priority));
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
index b46d9f7..0171dd5 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/EntityType.php
@@ -237,37 +237,6 @@ class EntityType extends Plugin {
   public $route_base_path;
 
   /**
-   * The base menu router path to which the entity admin user interface responds.
-   *
-   * It can be used to generate UI links and to attach additional router items
-   * to the entity UI in a generic fashion.
-   *
-   * @var string (optional)
-   */
-  public $menu_base_path;
-
-  /**
-   * The menu router path to be used to view the entity.
-   *
-   * @var string (optional)
-   */
-  public $menu_view_path;
-
-  /**
-   * The menu router path to be used to edit the entity.
-   *
-   * @var string (optional)
-   */
-  public $menu_edit_path;
-
-  /**
-   * A string identifying the menu loader in the router path.
-   *
-   * @var string (optional)
-   */
-  public $menu_path_wildcard;
-
-  /**
    * Link templates using the URI template syntax.
    *
    * Links are an array of standard link relations to the URI template that
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index e842208..9d20fb9 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -191,7 +191,6 @@ public function uri($rel = 'canonical') {
 
     // Pass the entity data to url() so that alter functions do not need to
     // look up this entity again.
-    $uri['options']['entity_type'] = $this->entityType;
     $uri['options']['entity'] = $this;
     return $uri;
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
index 09261a6..0c31999 100644
--- a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -59,13 +60,29 @@ public function __construct(AccessManager $access_manager, AccountInterface $cur
    */
   public function onKernelRequestAccessCheck(GetResponseEvent $event) {
     $request = $event->getRequest();
+
+    // The controller is being handled by the HTTP kernel, so add an attribute
+    // to tell us this is the controller request.
+    $request->attributes->set('_controller_request', TRUE);
+
     if (!$request->attributes->has(RouteObjectInterface::ROUTE_OBJECT)) {
       // If no Route is available it is likely a static resource and access is
       // handled elsewhere.
       return;
     }
 
-    $access = $this->accessManager->check($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT), $request, $this->currentUser);
+    // Wrap this in a try/catch to ensure the '_controller_request' attribute
+    // can always be removed.
+    try {
+      $access = $this->accessManager->check($request->attributes->get(RouteObjectInterface::ROUTE_OBJECT), $request, $this->currentUser);
+    }
+    catch (\Exception $e) {
+      $request->attributes->remove('_controller_request');
+      throw $e;
+    }
+
+    $request->attributes->remove('_controller_request');
+
     if (!$access) {
       throw new AccessDeniedHttpException();
     }
diff --git a/core/lib/Drupal/Core/Field/LegacyFieldTypeDiscoveryDecorator.php b/core/lib/Drupal/Core/Field/LegacyFieldTypeDiscoveryDecorator.php
index 381f69e..6e43b3f 100644
--- a/core/lib/Drupal/Core/Field/LegacyFieldTypeDiscoveryDecorator.php
+++ b/core/lib/Drupal/Core/Field/LegacyFieldTypeDiscoveryDecorator.php
@@ -66,7 +66,7 @@ public function getDefinitions() {
           $definition['id'] = $plugin_id;
           $definition['provider'] = $module;
           $definition['configurable'] = TRUE;
-          $definition['list_class'] = '\Drupal\Core\Field\Plugin\Field\FieldType\LegacyConfigFieldItemList';
+          $definition += array('list_class' => '\Drupal\Core\Field\Plugin\Field\FieldType\LegacyConfigFieldItemList');
           $definitions[$plugin_id] = $definition;
         }
       }
diff --git a/core/lib/Drupal/Core/Installer/Installer.php b/core/lib/Drupal/Core/Installer/Installer.php
new file mode 100644
index 0000000..80ff3c2
--- /dev/null
+++ b/core/lib/Drupal/Core/Installer/Installer.php
@@ -0,0 +1,384 @@
+<?php
+
+namespace Drupal\Core\Installer;
+
+/**
+ * An implementation of the InstallerInterface that provides a more explicit
+ * wrapper around the install_drupal function, used for non-interactive
+ * installs.
+ */
+class Installer implements InstallerInterface {
+
+  /**
+   * @var string
+   */
+  protected $profile;
+
+  /**
+   * @var string
+   */
+  protected $locale;
+
+  /**
+   * @var string
+   */
+  protected $accountPassword;
+
+  /**
+   * @var string
+   */
+  protected $accountUsername;
+
+  /**
+   * @var string
+   */
+  protected $accountMail;
+
+  /**
+   * @var string
+   */
+  protected $cleanUrls;
+
+  /**
+   * @var string
+   */
+  protected $siteMail;
+
+  /**
+   * @var string
+   */
+  protected $siteName;
+
+  /**
+   * @var string
+   */
+  protected $defaultDbDriver;
+
+  /**
+   * @var array
+   */
+  protected $defaultDbSettings;
+
+  /**
+   * @var string
+   */
+  private $installCallback;
+
+  /**
+   * @var string
+   */
+  private $siteHostName;
+
+  /**
+   * @var string
+   */
+  private $sitePath;
+
+  /**
+
+  /**
+   * {@inheritdoc}
+   */
+  function install() {
+    $this->exportGlobals();
+    $this->validate();
+    return $this->installCallback($this->installDrupalSettings());
+  }
+
+  /**
+   * Validates that all required properties have been set.
+   *
+   * @throws InstallerException
+   * @return bool
+   */
+  function validate() {
+    $class_info = new \ReflectionClass($this);
+    $properties = array();
+    $missing_properties = array();
+    foreach ($class_info->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) {
+      $var_name = $property->getName();
+      if ($this->{$var_name} === NULL) {
+        $missing_properties[] = $var_name;
+      }
+    }
+
+    if (!empty($missing_properties)) {
+      $message = "All properties must be set! The following have not been set: ";
+      $message += implode(', ', $missing_properties);
+      throw new InstallerException($message);
+    }
+    return TRUE;
+  }
+
+  /**
+   * Calls the install callback, which will default to install_drupal() outside
+   * of a testing scenario.
+   *
+   * @param array $settings
+   */
+  protected function installCallback($settings) {
+    if ($this->installCallback) {
+      $callback = $this->installCallback;
+    }
+    else {
+      if (!function_exists('install_drupal')) {
+        require $this->drupalRoot() . '/core/includes/install.core.inc';
+      }
+      $callback = 'install_drupal';
+    }
+    // Unfortunately, a chdir is necessary for the installtion to work, due to
+    // core's inconsistant use of the DRUPAL_ROOT constant.
+    // TODO: Fix Drupal's use of DRUPAL_ROOT so that this isn't necessary.
+    chdir($this->drupalRoot());
+    return call_user_func($callback, $settings);
+  }
+
+  protected function drupalRoot() {
+    return substr(__FILE__, 0, strpos(__FILE__, "core/lib/Drupal/Core"));
+  }
+
+  /**
+   * Setter for the install callback. Should only be used in tests.
+   *
+   * @param callable $callback
+   */
+  public function setInstallCallback($callback) {
+    $this->installCallback = $callback;
+  }
+
+  /**
+   * Creates the global variables necessary for install_drupal to work, since it
+   * needs more than it claims in it's function signature.
+   */
+  protected function exportGlobals() {
+    define('MAINTENANCE_MODE', 'install');
+
+    if ($hostname = $this->getSiteHostName()) {
+      $_SERVER['HTTP_HOST'] = $hostname;
+    }
+    $_SERVER['SCRIPT_NAME'] = $this->getSitePath()  . '/index.php';
+  }
+
+  /**
+   * Returns the installation settings in the format necessary for 
+   * install_drupal().
+   *
+   * @return array
+   */
+  protected function installDrupalSettings() {
+    return array(
+      'parameters' => array(
+        'profile' => $this->getProfile(),
+        'locale' => $this->getLocale(),
+      ),
+      'forms' => array(
+        'install_settings_form' => array(
+          'driver' => $this->getDefaultDbDriver(),
+          $this->getDefaultDbDriver() => $this->getDefaultDbSettings(),
+          'op' => 'Save and continue'
+        ),
+        'install_configure_form' => array(
+          'account' => array(
+            'mail' => $this->getAccountMail(),
+            'name' =>  $this->getAccountUsername(),
+            'pass' => array(
+              'pass1' => $this->getAccountPassword(),
+              'pass2' => $this->getAccountPassword(),
+            )
+          ),
+          'clean_url' => $this->getCleanUrls(),
+          'op' => 'Save and continue',
+          'site_mail' => $this->getSiteMail(),
+          'site_name' => $this->getSiteName(),
+          'update_status_module' => array(
+            '1' => true,
+            '2' => true
+          ),
+        ),
+      ),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSite($hostname, $path = '/') {
+    $this->siteHostName = $hostname;
+    $this->sitePath = $path;
+    return $this;
+  }
+
+  /**
+   * Returns the hostname of the site to use for the install. If NULL, the 
+   * default site should be used.
+   *
+   * @return string|null
+   */
+  protected function getSiteHostName() {
+    return $this->siteHostName;
+  }
+
+  /**
+   * Returns the path of the site to use for the install, if using a 
+   * sub-directory based multisite. Will return "/" if not set.
+   *
+   * @return string
+   */
+  protected function getSitePath() {
+    return $this->sitePath;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProfile($profile) {
+    $this->profile = $profile;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProfile() {
+    return $this->profile;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setAccountMail($accountMail) {
+    $this->accountMail = $accountMail;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAccountMail() {
+    return $this->accountMail;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setAccountPassword($accountPassword) {
+    $this->accountPassword = $accountPassword;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAccountPassword() {
+    return $this->accountPassword;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setAccountUsername($accountUsername) {
+    $this->accountUsername = $accountUsername;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAccountUsername() {
+    return $this->accountUsername;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCleanUrls($cleanUrls) {
+    $this->cleanUrls = $cleanUrls;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCleanUrls() {
+    return $this->cleanUrls;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDefaultDbSettings($driver, $defaultDbSettings) {
+    $this->defaultDbDriver = $driver;
+    if ($defaultDbSettings['host'] === 'localhost') {
+      // When using "localhost", running the acceptance test through the
+      // simpletest UI, the install fails with:
+      //
+      //   "PDO::__construct(): [2002] No such file or directory"
+      //
+      // Changing the hostname to to 127.0.0.1 fixes it. However, when running
+      // the test through the phpunit cli it seems to work fine.
+      $defaultDbSettings['host'] = '127.0.0.1';
+    }
+    $this->defaultDbSettings = $defaultDbSettings;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultDbDriver() {
+    return $this->defaultDbDriver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultDbSettings() {
+    return $this->defaultDbSettings;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLocale($locale) {
+    $this->locale = $locale;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLocale() {
+    return $this->locale;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSiteMail($siteMail) {
+    $this->siteMail = $siteMail;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSiteMail() {
+    return $this->siteMail;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSiteName($siteName) {
+    $this->siteName = $siteName;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSiteName() {
+    return $this->siteName;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Installer/InstallerInterface.php b/core/lib/Drupal/Core/Installer/InstallerInterface.php
new file mode 100644
index 0000000..a37570b
--- /dev/null
+++ b/core/lib/Drupal/Core/Installer/InstallerInterface.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Drupal\Core\Installer;
+
+/**
+ * Provides an interface for a code-based installation of Drupal.
+ */
+interface InstallerInterface {
+
+  /**
+   * Runs the installer.
+   *
+   * @return bool
+   */
+  public function install();
+
+  /**
+   * @return string
+   */
+  public function getSiteName();
+
+  /**
+   * @param string $siteName
+   */
+  public function setSiteName($siteName);
+
+  /**
+   * @return string
+   */
+  public function getAccountUsername();
+
+  /**
+   * @param string $accountUsername
+   */
+  public function setAccountUsername($accountUsername);
+
+  /**
+   * @return string
+   */
+  public function getDefaultDbDriver();
+
+  /**
+   * @return array
+   */
+  public function getDefaultDbSettings();
+
+  /**
+   * @param array $defaultDbSettings
+   */
+  public function setDefaultDbSettings($driver, $defaultDbSettings);
+
+  /**
+   * @param string $cleanUrls
+   */
+  public function setCleanUrls($cleanUrls);
+
+  /**
+   * @return string
+   */
+  public function getCleanUrls();
+
+  /**
+   * @return string
+   */
+  public function getAccountPassword();
+
+  /**
+   * @param string $accountPassword
+   */
+  public function setAccountPassword($accountPassword);
+
+  /**
+   * @return string
+   */
+  public function getAccountMail();
+
+  /**
+   * @param string $accountMail
+   */
+  public function setAccountMail($accountMail);
+
+  /**
+   * @return string
+   */
+  public function getProfile();
+
+  /**
+   * @param string $profile
+   */
+  public function setProfile($profile);
+
+  /**
+   * @param string $hostname
+   * @param string $path
+   */
+  public function setSite($hostname, $path);
+
+  /**
+   * @return string
+   */
+  public function getSiteMail();
+
+
+  /**
+   * @param string $siteMail
+   */
+  public function setSiteMail($siteMail);
+
+  /**
+   * @return string
+   */
+  public function getLocale();
+
+  /**
+   * @param string $locale
+   */
+  public function setLocale($locale);
+}
diff --git a/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php b/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php
index 347a877..9e69001 100644
--- a/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php
+++ b/core/lib/Drupal/Core/PathProcessor/OutboundPathProcessorInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\PathProcessor;
 
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
 
 /**
  * Defines an interface for classes that process the outbound path.
diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
index 92bc3f0..ce93bab 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
@@ -121,8 +121,13 @@ public function __construct($subdir, \Traversable $namespaces, $plugin_definitio
    *   Cache key prefix to use, the language code will be appended
    *   automatically.
    * @param array $cache_tags
-   *   (optional) When providing a list of cache tags, the cached definitions
-   *   are tagged and are used to clear the cache.
+   *   (optional) When providing a list of cache tags, the cached plugin
+   *   definitions are tagged with the provided cache tags. These cache tags can
+   *   then be used to clear the corresponding cached plugin definitions. Note
+   *   that this should be used with care! For clearing all cached plugin
+   *   definitions of a plugin manager, call that plugin manager's
+   *   clearCachedDefinitions() method. Only use cache tags when cached plugin
+   *   definitions should be cleared along with other, related cache entries.
    */
   public function setCacheBackend(CacheBackendInterface $cache_backend, LanguageManager $language_manager, $cache_key_prefix, array $cache_tags = array()) {
     $this->languageManager = $language_manager;
diff --git a/core/lib/Drupal/Core/RouteProcessor/OutboundRouteProcessorInterface.php b/core/lib/Drupal/Core/RouteProcessor/OutboundRouteProcessorInterface.php
new file mode 100644
index 0000000..c9bda24
--- /dev/null
+++ b/core/lib/Drupal/Core/RouteProcessor/OutboundRouteProcessorInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface.
+ */
+
+namespace Drupal\Core\RouteProcessor;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Defines an interface for classes that process the outbound route.
+ */
+interface OutboundRouteProcessorInterface {
+
+  /**
+   * Processes the outbound route.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The outbound route to process.
+   *
+   * @param array $parameters
+   *   An array of parameters to be passed to the route compiler. Passed by
+   *   reference.
+   *
+   * @return
+   *   The processed path.
+   */
+  public function processOutbound(Route $route, array &$parameters);
+
+}
diff --git a/core/lib/Drupal/Core/RouteProcessor/RouteProcessorManager.php b/core/lib/Drupal/Core/RouteProcessor/RouteProcessorManager.php
new file mode 100644
index 0000000..43071be
--- /dev/null
+++ b/core/lib/Drupal/Core/RouteProcessor/RouteProcessorManager.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\RouteProcessor\RouteProcessorManager.
+ */
+
+namespace Drupal\Core\RouteProcessor;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Route processor manager.
+ *
+ * Holds an array of route processor objects and uses them to sequentially
+ * process an outbound route, in order of processor priority.
+ */
+class RouteProcessorManager implements OutboundRouteProcessorInterface {
+
+  /**
+   * Holds the array of outbound processors to cycle through.
+   *
+   * @var array
+   *   An array whose keys are priorities and whose values are arrays of path
+   *   processor objects.
+   */
+  protected $outboundProcessors = array();
+
+  /**
+   * Holds the array of outbound processors, sorted by priority.
+   *
+   * @var array
+   *   An array of path processor objects.
+   */
+  protected $sortedOutbound = array();
+
+  /**
+   * Adds an outbound processor object to the $outboundProcessors property.
+   *
+   * @param \Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface $processor
+   *   The processor object to add.
+   *
+   * @param int $priority
+   *   The priority of the processor being added.
+   */
+  public function addOutbound(OutboundRouteProcessorInterface $processor, $priority = 0) {
+    $this->outboundProcessors[$priority][] = $processor;
+    $this->sortedOutbound = array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processOutbound(Route $route, array &$parameters) {
+    $processors = $this->getOutbound();
+    foreach ($processors as $processor) {
+      $processor->processOutbound($route, $parameters);
+    }
+  }
+
+  /**
+   * Returns the sorted array of outbound processors.
+   *
+   * @return array
+   *   An array of processor objects.
+   */
+  protected function getOutbound() {
+    if (empty($this->sortedOutbound)) {
+      $this->sortedOutbound = $this->sortProcessors();
+    }
+
+    return $this->sortedOutbound;
+  }
+
+  /**
+   * Sorts the processors according to priority.
+   */
+  protected function sortProcessors() {
+    $sorted = array();
+    krsort($this->outboundProcessors);
+
+    foreach ($this->outboundProcessors as $processors) {
+      $sorted = array_merge($sorted, $processors);
+    }
+    return $sorted;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/NullGenerator.php b/core/lib/Drupal/Core/Routing/NullGenerator.php
index b6e2609..1430f1f 100644
--- a/core/lib/Drupal/Core/Routing/NullGenerator.php
+++ b/core/lib/Drupal/Core/Routing/NullGenerator.php
@@ -9,6 +9,7 @@
 
 use Symfony\Component\Routing\RequestContext;
 use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\Route;
 
 /**
  * No-op implementation of a Url Generator, needed for backward compatibility.
diff --git a/core/lib/Drupal/Core/Routing/UrlGenerator.php b/core/lib/Drupal/Core/Routing/UrlGenerator.php
index bf6956f..1bb4b53 100644
--- a/core/lib/Drupal/Core/Routing/UrlGenerator.php
+++ b/core/lib/Drupal/Core/Routing/UrlGenerator.php
@@ -19,6 +19,7 @@
 use Drupal\Component\Utility\Url;
 use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
+use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
 
 /**
  * Generates URLs from route names and parameters.
@@ -40,6 +41,13 @@ class UrlGenerator extends ProviderBasedGenerator implements UrlGeneratorInterfa
   protected $pathProcessor;
 
   /**
+   * The route processor.
+   *
+   * @var \Drupal\Tests\Core\RouteProcessor\OutboundRouteProcessorInterface
+   */
+  protected $routeProcessor;
+
+  /**
    * The base path to use for urls.
    *
    * @var string
@@ -77,10 +85,11 @@ class UrlGenerator extends ProviderBasedGenerator implements UrlGeneratorInterfa
    * @param \Symfony\Component\HttpKernel\Log\LoggerInterface $logger
    *   An optional logger for recording errors.
    */
-  public function __construct(RouteProviderInterface $provider, OutboundPathProcessorInterface $path_processor, ConfigFactory $config, Settings $settings, LoggerInterface $logger = NULL) {
+  public function __construct(RouteProviderInterface $provider, OutboundPathProcessorInterface $path_processor, OutboundRouteProcessorInterface $route_processor, ConfigFactory $config, Settings $settings, LoggerInterface $logger = NULL) {
     parent::__construct($provider, $logger);
 
     $this->pathProcessor = $path_processor;
+    $this->routeProcessor = $route_processor;
     $this->mixedModeSessions = $settings->get('mixed_mode_sessions', FALSE);
     $allowed_protocols = $config->get('system.filter')->get('protocols') ?: array('http', 'https');
     Url::setAllowedProtocols($allowed_protocols);
@@ -167,10 +176,13 @@ public function generate($name, $parameters = array(), $absolute = FALSE) {
   public function generateFromRoute($name, $parameters = array(), $options = array()) {
     $absolute = !empty($options['absolute']);
     $route = $this->getRoute($name);
+    $this->processRoute($route, $parameters);
+
     // Symfony adds any parameters that are not path slugs as query strings.
     if (isset($options['query']) && is_array($options['query'])) {
       $parameters = (array) $parameters + $options['query'];
     }
+
     $path = $this->getInternalPathFromRoute($route, $parameters);
     $path = $this->processPath($path, $options);
     $fragment = '';
@@ -179,6 +191,7 @@ public function generateFromRoute($name, $parameters = array(), $options = array
         $fragment = '#' . $fragment;
       }
     }
+
     $base_url = $this->context->getBaseUrl();
     if (!$absolute || !$host = $this->context->getHost()) {
       return $base_url . $path . $fragment;
@@ -336,6 +349,19 @@ protected function processPath($path, &$options = array()) {
   }
 
   /**
+   * Passes the route to the processor manager for altering before complation.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route object to process.
+   *
+   * @param array $parameters
+   *   An array of parameters to be passed to the route compiler.
+   */
+  protected function processRoute(SymfonyRoute $route, array &$parameters) {
+    $this->routeProcessor->processOutbound($route, $parameters);
+  }
+
+  /**
    * Returns whether or not the url generator has been initialized.
    *
    * @return bool
diff --git a/core/modules/aggregator/aggregator.local_actions.yml b/core/modules/aggregator/aggregator.local_actions.yml
new file mode 100644
index 0000000..ca3d850
--- /dev/null
+++ b/core/modules/aggregator/aggregator.local_actions.yml
@@ -0,0 +1,17 @@
+aggregator.feed_add:
+  route_name: aggregator.feed_add
+  title: 'Add feed'
+  appears_on:
+    - 'aggregator.admin_overview'
+
+aggregator.category_add:
+  route_name: aggregator.category_add
+  title: 'Add category'
+  appears_on:
+    - 'aggregator.admin_overview'
+
+aggregator.opml_add:
+  route_name: aggregator.opml_add
+  title: 'Import OPML'
+  appears_on:
+    - 'aggregator.admin_overview'
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 045c156..726f228 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -97,21 +97,6 @@ function aggregator_menu() {
     'route_name' => 'aggregator.admin_overview',
     'weight' => 10,
   );
-  $items['admin/config/services/aggregator/add/feed'] = array(
-    'title' => 'Add feed',
-    'route_name' => 'aggregator.feed_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
-  $items['admin/config/services/aggregator/add/category'] = array(
-    'title' => 'Add category',
-    'type' => MENU_LOCAL_ACTION,
-    'route_name' => 'aggregator.category_add',
-  );
-  $items['admin/config/services/aggregator/add/opml'] = array(
-    'title' => 'Import OPML',
-    'type' => MENU_LOCAL_ACTION,
-    'route_name' => 'aggregator.opml_add',
-  );
   $items['admin/config/services/aggregator/remove/%aggregator_feed'] = array(
     'title' => 'Remove items',
     'route_name' => 'aggregator.feed_items_delete',
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php
index 2baf1fe..426b479 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php
@@ -37,8 +37,10 @@
  *   base_table = "custom_block",
  *   revision_table = "custom_block_revision",
  *   route_base_path = "admin/structure/block/custom-blocks/manage/{bundle}",
- *   menu_base_path = "block/%custom_block",
- *   menu_edit_path = "block/%custom_block",
+ *   links = {
+ *     "canonical" = "/block/{custom_block}",
+ *     "edit-form" = "/block/{custom_block}"
+ *   },
  *   fieldable = TRUE,
  *   translatable = TRUE,
  *   entity_keys = {
@@ -177,19 +179,6 @@ protected function init() {
   /**
    * {@inheritdoc}
    */
-  public function uri() {
-    return array(
-      'path' => 'block/' . $this->id(),
-      'options' => array(
-        'entity_type' => $this->entityType,
-        'entity' => $this,
-      )
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function preSave(EntityStorageControllerInterface $storage_controller) {
     parent::preSave($storage_controller);
 
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php
index 387c2a3..260da45 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php
@@ -15,11 +15,13 @@
 class CommentPreviewTest extends CommentTestBase {
 
   /**
-   * Modules to enable.
+   * The profile to install as a basis for testing.
    *
-   * @var array
+   * Using the standard profile to test user picture display in comments.
+   *
+   * @var string
    */
-  public static $modules = array('image');
+  protected $profile = 'standard';
 
   public static function getInfo() {
     return array(
@@ -32,10 +34,6 @@ public static function getInfo() {
   function setUp() {
     parent::setUp();
 
-    // Create user picture field.
-    module_load_install('user');
-    user_install_picture_field();
-
     // Add the basic_html filter format from the standard install profile.
     $filter_format_storage_controller = $this->container->get('entity.manager')->getStorageController('filter_format');
     $filter_format = $filter_format_storage_controller->create(array(
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
index 7944fdd..a90be4a 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
@@ -68,7 +68,7 @@ function setupBundle() {
    * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::getTranslatorPermission().
    */
   protected function getTranslatorPermissions() {
-    return array_merge(parent::getTranslatorPermissions(), array('post comments', 'administer comments'));
+    return array_merge(parent::getTranslatorPermissions(), array('post comments', 'administer comments', 'access comments'));
   }
 
   /**
@@ -134,11 +134,11 @@ protected function assertPublishedStatus() {
     $languages = language_list();
 
     // Check that simple users cannot see unpublished field translations.
-    $path = $this->controller->getViewPath($entity);
+    $uri = $entity->uri();
     foreach ($this->langcodes as $index => $langcode) {
       $translation = $this->getTranslation($entity, $langcode);
       $value = $this->getValue($translation, 'comment_body', $langcode);
-      $this->drupalGet($path, array('language' => $languages[$langcode]));
+      $this->drupalGet($uri['path'], array('language' => $languages[$langcode]));
       if ($index > 0) {
         $this->assertNoRaw($value, 'Unpublished field translation is not shown.');
       }
diff --git a/core/modules/contact/contact.local_actions.yml b/core/modules/contact/contact.local_actions.yml
new file mode 100644
index 0000000..c917e7f
--- /dev/null
+++ b/core/modules/contact/contact.local_actions.yml
@@ -0,0 +1,6 @@
+contact.category_add:
+  route_name: contact.category_add
+  title: 'Add category'
+  weight: 1
+  appears_on:
+    - contact.category_list
diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module
index 17900fa..66a80ec 100644
--- a/core/modules/contact/contact.module
+++ b/core/modules/contact/contact.module
@@ -63,12 +63,6 @@ function contact_menu() {
     'description' => 'Create a system contact form and set up categories for the form to use.',
     'route_name' => 'contact.category_list',
   );
-  $items['admin/structure/contact/add'] = array(
-    'title' => 'Add category',
-    'route_name' => 'contact.category_add',
-    'type' => MENU_LOCAL_ACTION,
-    'weight' => 1,
-  );
   $items['admin/structure/contact/manage/%contact_category'] = array(
     'title' => 'Edit contact category',
     'route_name' => 'contact.category_edit',
diff --git a/core/modules/contact/contact.routing.yml b/core/modules/contact/contact.routing.yml
index 7448192..f12d67e 100644
--- a/core/modules/contact/contact.routing.yml
+++ b/core/modules/contact/contact.routing.yml
@@ -17,6 +17,7 @@ contact.category_add:
   path: '/admin/structure/contact/add'
   defaults:
     _entity_form: contact_category.add
+    _title: 'Add category'
   requirements:
     _permission: 'administer contact forms'
 
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 66bb500..ae4a86e 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -78,40 +78,33 @@ function content_translation_language_types_info_alter(array &$language_types) {
 function content_translation_entity_info_alter(array &$entity_info) {
   // Provide defaults for translation info.
   foreach ($entity_info as $entity_type => &$info) {
-    if (empty($info['translatable'])) {
-      continue;
-    }
+    if (!empty($info['translatable'])) {
+      // Every fieldable entity type must have a translation controller class,
+      // no matter if it is enabled for translation or not. As a matter of fact
+      // we might need it to correctly switch field translatability when a field
+      // is shared accross different entities.
+      $info['controllers'] += array('translation' => 'Drupal\content_translation\ContentTranslationController');
+
+      if (!isset($info['translation']['content_translation'])) {
+        $info['translation']['content_translation'] = array();
+      }
 
-    if (!isset($info['translation']['content_translation'])) {
-      $info['translation']['content_translation'] = array();
-    }
+      if (!empty($info['links']['canonical'])) {
+        // Provide default links for the translation paths.
+        $info['links'] += array(
+          'drupal:content-translation-overview' => $info['links']['canonical'] . '/translations',
+        );
 
-    // Every fieldable entity type must have a translation controller class, no
-    // matter if it is enabled for translation or not. As a matter of fact we
-    // might need it to correctly switch field translatability when a field is
-    // shared accross different entities.
-    $info['controllers'] += array('translation' => 'Drupal\content_translation\ContentTranslationController');
-
-    // If no menu base path is provided we default to the usual
-    // "entity_type/%entity_type" pattern.
-    if (!isset($info['menu_base_path'])) {
-      $path = "$entity_type/%$entity_type";
-      $info['menu_base_path'] = $path;
+        $parts = explode('/', trim($info['links']['canonical'], '/'));
+        $entity_position = array_search("{{$entity_type}}", $parts);
+        if ($entity_position !== FALSE) {
+          $info['translation']['content_translation'] += array(
+            'access_callback' => 'content_translation_translate_access',
+            'access_arguments' => array($entity_position),
+          );
+        }
+      }
     }
-
-    $path = $info['menu_base_path'];
-
-    $info += array(
-      'menu_view_path' => $path,
-      'menu_edit_path' => "$path/edit",
-      'menu_path_wildcard' => "%$entity_type",
-    );
-
-    $entity_position = count(explode('/', $path)) - 1;
-    $info['translation']['content_translation'] += array(
-      'access_callback' => 'content_translation_translate_access',
-      'access_arguments' => array($entity_position),
-    );
   }
 }
 
@@ -156,6 +149,9 @@ function content_translation_entity_field_info_alter(&$info, $entity_type) {
 
 /**
  * Implements hook_menu().
+ *
+ * @todo Split this into route definition and menu link definition. See
+ *   https://drupal.org/node/1987882 and https://drupal.org/node/2047633.
  */
 function content_translation_menu() {
   $items = array();
@@ -164,7 +160,7 @@ function content_translation_menu() {
   foreach (entity_get_info() as $entity_type => $info) {
     // Provide the translation UI only for enabled types.
     if (content_translation_enabled($entity_type)) {
-      $path = $info['menu_base_path'];
+      $path = _content_translation_link_to_router_path($entity_type, $info['links']['canonical']);
       $entity_position = count(explode('/', $path)) - 1;
       $keys = array_flip(array('theme_callback', 'theme_arguments', 'access_callback', 'access_arguments', 'load_arguments'));
       $menu_info = array_intersect_key($info['translation']['content_translation'], $keys) + array('file' => 'content_translation.pages.inc');
@@ -228,6 +224,9 @@ function content_translation_menu() {
 
 /**
  * Implements hook_menu_alter().
+ *
+ * @todo Split this into route definition and menu link definition. See
+ *   https://drupal.org/node/1987882 and https://drupal.org/node/2047633.
  */
 function content_translation_menu_alter(array &$items) {
   // Clarify where translation settings are located.
@@ -237,7 +236,7 @@ function content_translation_menu_alter(array &$items) {
   // Check that the declared menu base paths are actually valid.
   foreach (entity_get_info() as $entity_type => $info) {
     if (content_translation_enabled($entity_type)) {
-      $path = $info['menu_base_path'];
+      $path = _content_translation_link_to_router_path($entity_type, $info['links']['canonical']);
 
       // If the base path is not defined we cannot provide the translation UI
       // for this entity type. In some cases the actual base path might not have
@@ -255,7 +254,7 @@ function content_translation_menu_alter(array &$items) {
       }
       else {
         $entity_position = count(explode('/', $path)) - 1;
-        $edit_path = $info['menu_edit_path'];
+        $edit_path = $path . '/edit';
 
         if (isset($items[$edit_path])) {
           // If the edit path is a default local task we need to find the parent
@@ -287,6 +286,24 @@ function content_translation_local_tasks_alter(&$local_tasks) {
 }
 
 /**
+ * Convert an entity canonical link to a router path.
+ *
+ * @param string $link
+ *   The entity link to be converted.
+ *
+ * @return string
+ *   The resulting router path. For instance "/node/{node}" is turned into
+ *   "node/%node".
+ *
+ * @todo Remove this and use the actual link values when all the Content
+ *   Translation code is adapted to the new routing system.
+ */
+function _content_translation_link_to_router_path($entity_type, $link) {
+  $path = preg_replace('|{([^}]+)}|', '%$1', trim($link, '/'));
+  return str_replace('%id', '%' . $entity_type, $path);
+}
+
+/**
  * Strips out menu loaders from the given path.
  *
  * @param string $path
@@ -482,9 +499,8 @@ function content_translation_set_config($entity_type, $bundle, $setting, $value)
  */
 function content_translation_enabled($entity_type, $bundle = NULL) {
   $enabled = FALSE;
-  $info = entity_get_info($entity_type);
 
-  if (!empty($info['translatable'])) {
+  if (content_translation_supported($entity_type)) {
     $bundles = !empty($bundle) ? array($bundle) : array_keys(entity_get_bundles($entity_type));
     foreach ($bundles as $bundle) {
       if (content_translation_get_config($entity_type, $bundle, 'enabled')) {
@@ -498,31 +514,17 @@ function content_translation_enabled($entity_type, $bundle = NULL) {
 }
 
 /**
- * Returns all the translatable entity types.
+ * Checks whether an entity type supports translation.
+ *
+ * @param string $entity_type
+ *   The entity type.
  *
- * @return array
- *   An array of entity types keyed by entity type.
+ * @return bool
+ *   TRUE if an entity type is supported, FALSE otherwise.
  */
-function content_translation_types_translatable() {
-  $entity_types = &drupal_static(__FUNCTION__, array());
-
-  foreach (entity_get_info() as $entity_type => $info) {
-    if (content_translation_enabled($entity_type)) {
-      // Lazy load router items.
-      if (!isset($items)) {
-        $items = menu_get_router();
-      }
-      // Check whether the required paths are defined. We need to strip out the
-      // menu loader and replace it with a plain "%" as router items have no
-      // menu loader in them.
-      $path = _content_translation_menu_strip_loaders($info['menu_base_path']);
-      if (!empty($items[$path]) && !empty($items[$path . '/translations'])) {
-        $entity_types[$entity_type] = $entity_type;
-      }
-    }
-  }
-
-  return $entity_types;
+function content_translation_supported($entity_type) {
+  $info = entity_get_info($entity_type);
+  return !empty($info['translatable']) && !empty($info['links']['drupal:content-translation-overview']);
 }
 
 /**
diff --git a/core/modules/content_translation/content_translation.pages.inc b/core/modules/content_translation/content_translation.pages.inc
index f64599f..b6473ae 100644
--- a/core/modules/content_translation/content_translation.pages.inc
+++ b/core/modules/content_translation/content_translation.pages.inc
@@ -25,14 +25,21 @@ function content_translation_overview(EntityInterface $entity) {
   $translations = $entity->getTranslationLanguages();
   $field_ui = \Drupal::moduleHandler()->moduleExists('field_ui') && user_access('administer ' . $entity->entityType() . ' fields');
 
-  $path = $controller->getViewPath($entity);
-  $base_path = $controller->getBasePath($entity);
-  $edit_path = $controller->getEditPath($entity);
+  $rel = array();
+  foreach (array('canonical', 'edit-form', 'drupal:content-translation-overview') as $name) {
+    $rel[$name] = $entity->uri($name);
+  }
 
   $header = array(t('Language'), t('Translation'), t('Source language'), t('Status'), t('Operations'));
   $rows = array();
 
   if (language_multilingual()) {
+    // If we have a view path defined for the current entity get the switch
+    // links based on it.
+    if (!empty($rel['canonical'])) {
+      $links = _content_translation_get_switch_links($rel['canonical']['path']);
+    }
+
     // Determine whether the current entity is translatable.
     $translatable = FALSE;
     foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
@@ -45,16 +52,14 @@ function content_translation_overview(EntityInterface $entity) {
     foreach ($languages as $language) {
       $language_name = $language->name;
       $langcode = $language->id;
-      $add_path = $base_path . '/translations/add/' . $original . '/' . $langcode;
-      $translate_path = $base_path . '/translations/edit/' . $langcode;
-      $delete_path = $base_path . '/translations/delete/' . $langcode;
-
-      if ($base_path) {
-        $add_links = _content_translation_get_switch_links($add_path);
-        $edit_links = _content_translation_get_switch_links($edit_path);
-        $translate_links = _content_translation_get_switch_links($translate_path);
-        $delete_links = _content_translation_get_switch_links($delete_path);
-      }
+
+      $add_path = $rel['drupal:content-translation-overview']['path'] . '/add/' . $original . '/' . $langcode;
+      $translate_path = $rel['drupal:content-translation-overview']['path'] . '/edit/' . $langcode;
+
+      $add_links = _content_translation_get_switch_links($add_path);
+      $edit_links = _content_translation_get_switch_links($rel['edit-form']['path']);
+      $translate_links = _content_translation_get_switch_links($translate_path);
+      $delete_links = _content_translation_get_switch_links($rel['drupal:content-translation-overview']['path'] . '/delete/' . $langcode);
 
       $operations = array(
         'data' => array(
@@ -69,7 +74,7 @@ function content_translation_overview(EntityInterface $entity) {
         $source = isset($entity->translation[$langcode]['source']) ? $entity->translation[$langcode]['source'] : '';
         $is_original = $langcode == $original;
         $label = $entity->getTranslation($langcode)->label();
-        $link = isset($links->links[$langcode]['href']) ? $links->links[$langcode] : array('href' => $path, 'language' => $language);
+        $link = isset($links->links[$langcode]['href']) ? $links->links[$langcode] : array('href' => $rel['canonical']['path'], 'language' => $language);
         $row_title = l($label, $link['href'], $link);
 
         if (empty($link['href'])) {
@@ -79,8 +84,8 @@ function content_translation_overview(EntityInterface $entity) {
         // If the user is allowed to edit the entity we point the edit link to
         // the entity form, otherwise if we are not dealing with the original
         // language we point the link to the translation form.
-        if ($edit_path && $entity->access('update')) {
-          $links['edit'] = isset($edit_links->links[$langcode]['href']) ? $edit_links->links[$langcode] : array('href' => $edit_path, 'language' => $language);
+        if ($entity->access('update')) {
+          $links['edit'] = isset($edit_links->links[$langcode]['href']) ? $edit_links->links[$langcode] : array('href' => $rel['edit-form']['path'], 'language' => $language);
         }
         elseif (!$is_original && $controller->getTranslationAccess($entity, 'update')) {
           $links['edit'] = isset($translate_links->links[$langcode]['href']) ? $translate_links->links[$langcode] : array('href' => $translate_path, 'language' => $language);
@@ -246,11 +251,12 @@ function content_translation_prepare_translation(EntityInterface $entity, Langua
  */
 function content_translation_delete_confirm(array $form, array $form_state, EntityInterface $entity, Language $language) {
   $controller = content_translation_controller($entity->entityType());
+  $uri = $entity->uri('drupal:content-translation-overview');
 
   return confirm_form(
     $form,
     t('Are you sure you want to delete the @language translation of %label?', array('@language' => $language->name, '%label' => $entity->label())),
-    $controller->getEditPath($entity),
+    $uri['path'],
     t('This action cannot be undone.'),
     t('Delete'),
     t('Cancel')
@@ -271,9 +277,11 @@ function content_translation_delete_confirm_submit(array $form, array &$form_sta
   // Remove any existing path alias for the removed translation.
   // @todo This should be taken care of by the Path module.
   if (\Drupal::moduleHandler()->moduleExists('path')) {
-    $conditions = array('source' => $controller->getViewPath($entity), 'langcode' => $language->id);
+    $uri = $entity->uri();
+    $conditions = array('source' => $uri['path'], 'langcode' => $language->id);
     \Drupal::service('path.crud')->delete($conditions);
   }
 
-  $form_state['redirect'] = $controller->getBasePath($entity) . '/translations';
+  $uri = $entity->uri('drupal:content-translation-overview');
+  $form_state['redirect'] = $uri['path'];
 }
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
index c499806..a639f30 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
@@ -54,27 +54,6 @@ public function retranslate(EntityInterface $entity, $langcode = NULL) {
   }
 
   /**
-   * Implements ContentTranslationControllerInterface::getBasePath().
-   */
-  public function getBasePath(EntityInterface $entity) {
-    return $this->getPathInstance($this->entityInfo['menu_base_path'], $entity->id());
-  }
-
-  /**
-   * Implements ContentTranslationControllerInterface::getEditPath().
-   */
-  public function getEditPath(EntityInterface $entity) {
-    return isset($this->entityInfo['menu_edit_path']) ? $this->getPathInstance($this->entityInfo['menu_edit_path'], $entity->id()) : FALSE;
-  }
-
-  /**
-   * Implements ContentTranslationControllerInterface::getViewPath().
-   */
-  public function getViewPath(EntityInterface $entity) {
-    return isset($this->entityInfo['menu_view_path']) ? $this->getPathInstance($this->entityInfo['menu_view_path'], $entity->id()) : FALSE;
-  }
-
-  /**
    * Implements ContentTranslationControllerInterface::getTranslationAccess().
    */
   public function getTranslationAccess(EntityInterface $entity, $op) {
@@ -446,7 +425,9 @@ public function entityFormSourceChange($form, &$form_state) {
     $form_controller = content_translation_form_controller($form_state);
     $entity = $form_controller->getEntity();
     $source = $form_state['values']['source_langcode']['source'];
-    $path = $this->getBasePath($entity) . '/translations/add/' . $source . '/' . $form_controller->getFormLangcode($form_state);
+
+    $uri = $entity->uri('drupal:content-translation-overview');
+    $path = $uri['path'] . '/add/' . $source . '/' . $form_controller->getFormLangcode($form_state);
     $form_state['redirect'] = $path;
     $languages = language_list();
     drupal_set_message(t('Source language set to: %language', array('%language' => $languages[$source]->name)));
@@ -473,9 +454,9 @@ function entityFormDelete($form, &$form_state) {
   function entityFormDeleteTranslation($form, &$form_state) {
     $form_controller = content_translation_form_controller($form_state);
     $entity = $form_controller->getEntity();
-    $base_path = $this->getBasePath($entity);
+    $uri = $entity->uri('drupal:content-translation-overview');
     $form_langcode = $form_controller->getFormLangcode($form_state);
-    $form_state['redirect'] = $base_path . '/translations/delete/' . $form_langcode;
+    $form_state['redirect'] = $uri['path'] . '/delete/' . $form_langcode;
   }
 
   /**
@@ -488,17 +469,4 @@ protected function entityFormTitle(EntityInterface $entity) {
     return $entity->label();
   }
 
-  /**
-   * Returns an instance of the given path.
-   *
-   * @param $path
-   *   An internal path containing the entity id wildcard.
-   *
-   * @return string
-   *   The instantiated path.
-   */
-  protected function getPathInstance($path, $entity_id) {
-    $wildcard = $this->entityInfo['menu_path_wildcard'];
-    return str_replace($wildcard, $entity_id, $path);
-  }
 }
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationControllerInterface.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationControllerInterface.php
index d2d3c2c..ed68e8f 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationControllerInterface.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationControllerInterface.php
@@ -22,96 +22,27 @@
  * To make Content Translation automatically support an entity type some keys
  * may need to be defined, but none of them is required unless the entity path
  * is different from ENTITY_TYPE/%ENTITY_TYPE (e.g. taxonomy/term/1), in which
- * case at least the 'menu_base_path' key must be defined. This is used to
- * determine the view and edit paths if they follow the standard path patterns.
- * Otherwise the 'menu_view_path' and 'menu_edit_path' keys must be defined. If
- * an entity type is enabled for translation and no menu path key is defined,
- * the following defaults will be assumed:
- * - menu_base_path: ENTITY_TYPE/%ENTITY_TYPE
- * - menu_view_path: ENTITY_TYPE/%ENTITY_TYPE
- * - menu_edit_path: ENTITY_TYPE/%ENTITY_TYPE/edit
- * The menu base path is also used to reliably alter menu router information to
- * provide the translation overview page for any entity.
- * If the entity uses a menu loader different from %ENTITY_TYPE also the 'menu
- * path wildcard' info key needs to be defined.
+ * case at least the 'canonical' key in the 'links' entity info property must be
+ * defined.
  *
  * Every entity type needs a translation controller to be translated. This can
  * be specified through the "controllers['translation']" key in the entity
  * info. If an entity type is enabled for translation and no translation
- * controller is defined, Drupal\content_translation\ContentTranslationController
- * will be assumed. Every translation controller class must implement
- * Drupal\content_translation\ContentTranslationControllerInterface.
+ * controller is defined,
+ * \Drupal\content_translation\ContentTranslationController will be assumed.
+ * Every translation controller class must implement
+ * \Drupal\content_translation\ContentTranslationControllerInterface.
  *
  * If the entity paths match the default patterns above and there is no need for
  * an entity-specific translation controller class, Content Translation will
  * provide built-in support for the entity. It will still be required to enable
  * translation for each translatable bundle.
  *
- * Additionally some more entity info keys can be defined to further customize
- * the translation UI. The content translation info is an associative array that
- * has to match the following structure. Two nested arrays keyed respectively
- * by the 'translation' key and the 'content_translation' key. Elements:
- * - access callback: The access callback for the translation pages. Defaults to
- *   'entity_translation_translate_access'.
- * - access arguments: The access arguments for the translation pages. By
- *   default only the entity object is passed to the access callback.
- *
- * This is how entity info would look for a module defining a new translatable
- * entity type:
- * @code
- *   function mymodule_entity_info_alter(array &$info) {
- *     $info['myentity'] += array(
- *       'menu_base_path' => 'mymodule/myentity/%my_entity_loader',
- *       'menu_path_wildcard' => '%my_entity_loader',
- *       'translation' => array(
- *         'content_translation' => array(
- *           'access_callback' => 'mymodule_myentity_translate_access',
- *           'access_arguments' => array(2),
- *         ),
- *       ),
- *     );
- *     $info['myentity']['controllers'] += array('translation' => 'Drupal\mymodule\MyContentTranslationController');
- *   }
- * @endcode
- *
  * @see \Drupal\Core\Entity\EntityManagerInterface
  */
 interface ContentTranslationControllerInterface {
 
   /**
-   * Returns the base path for the current entity.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to the path should refer to.
-   *
-   * @return string
-   *   The entity base path.
-   */
-  public function getBasePath(EntityInterface $entity);
-
-  /**
-   * Returns the path of the entity edit form.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to the path should refer to.
-   *
-   * @return string
-   *   The entity edit path.
-   */
-  public function getEditPath(EntityInterface $entity);
-
-  /**
-   * Returns the path of the entity view page.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to the path should refer to.
-   *
-   * @return string
-   *   The entity view path.
-   */
-  public function getViewPath(EntityInterface $entity);
-
-  /**
    * Checks if the user can perform the given operation on translations of the
    * wrapped entity.
    *
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php
index 21c352a..f7da9a7 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Plugin/Derivative/ContentTranslationLocalTasks.php
@@ -72,7 +72,7 @@ public static function create(ContainerInterface $container, $base_plugin_id) {
   public function getDerivativeDefinitions(array $base_plugin_definition) {
     // Create tabs for all possible entity types.
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
-      if ($entity_info['translatable'] && isset($entity_info['translation'])) {
+      if (!empty($entity_info['translatable'])) {
         // Find the route name for the translation overview.
         $translation_route_name = "content_translation.translation_overview_$entity_type";
         $translation_tab = $translation_route_name;
@@ -92,8 +92,8 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
    */
   public function alterLocalTasks(array &$local_tasks) {
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
-      if ($entity_info['translatable'] && isset($entity_info['translation'])) {
-        $path = '/' . preg_replace('/%(.*)/', '{$1}', $entity_info['menu_base_path']);
+      if (!empty($entity_info['translatable']) && !empty($entity_info['links']['canonical'])) {
+        $path = $entity_info['links']['canonical'];
         if ($routes = $this->routeProvider->getRoutesByPattern($path)->all()) {
           // Find the route name for the entity page.
           $entity_route_name = key($routes);
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
index 63033d4..368dc27 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Routing/ContentTranslationRouteSubscriber.php
@@ -39,8 +39,9 @@ public function __construct(EntityManagerInterface $entityManager) {
    */
   protected function routes(RouteCollection $collection) {
     foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
-      if ($entity_info['translatable'] && isset($entity_info['translation'])) {
-        $path = '/' . str_replace($entity_info['menu_path_wildcard'], '{' . $entity_type . '}', $entity_info['menu_base_path']) . '/translations';
+      if (!empty($entity_info['translatable']) && !empty($entity_info['links']['drupal:content-translation-overview'])) {
+        $path = $entity_info['links']['drupal:content-translation-overview'];
+
         $route = new Route(
          $path,
           array(
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php
index c24e064..8a1b198 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php
@@ -52,6 +52,9 @@ protected function doTestBasicTranslation() {
     $this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
     $this->assertTrue($entity, 'Entity found in the database.');
+    $uri = $entity->uri();
+    $this->drupalGet($uri['path']);
+    $this->assertResponse(200, 'Entity URL is valid.');
 
     $translation = $this->getTranslation($entity, $default_langcode);
     foreach ($values[$default_langcode] as $property => $value) {
@@ -65,8 +68,8 @@ protected function doTestBasicTranslation() {
     $langcode = 'it';
     $values[$langcode] = $this->getNewEntityValues($langcode);
 
-    $base_path = $this->controller->getBasePath($entity);
-    $path = $langcode . '/' . $base_path . '/translations/add/' . $default_langcode . '/' . $langcode;
+    $uri = $entity->uri('drupal:content-translation-overview');
+    $path = $langcode . '/' . $uri['path'] . '/add/' . $default_langcode . '/' . $langcode;
     $this->drupalPostForm($path, $this->getEditValues($values, $langcode), $this->getFormSubmitAction($entity));
     if ($this->testLanguageSelector) {
       $this->assertNoFieldByXPath('//select[@id="edit-langcode"]', NULL, 'Language selector correclty disabled on translations.');
@@ -77,7 +80,7 @@ protected function doTestBasicTranslation() {
     $langcode = 'fr';
     $source_langcode = 'it';
     $edit = array('source_langcode[source]' => $source_langcode);
-    $path = $langcode . '/' . $base_path . '/translations/add/' . $default_langcode . '/' . $langcode;
+    $path = $langcode . '/' . $uri['path'] . '/add/' . $default_langcode . '/' . $langcode;
     $this->drupalPostForm($path, $edit, t('Change'));
     $this->assertFieldByXPath("//input[@name=\"{$this->fieldName}[0][value]\"]", $values[$source_langcode][$this->fieldName][0]['value'], 'Source language correctly switched.');
 
@@ -104,8 +107,8 @@ protected function doTestBasicTranslation() {
    */
   protected function doTestTranslationOverview() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
-    $path = $this->controller->getBasePath($entity) . '/translations';
-    $this->drupalGet($path);
+    $uri = $entity->uri('drupal:content-translation-overview');
+    $this->drupalGet($uri['path']);
 
     foreach ($this->langcodes as $langcode) {
       if ($entity->hasTranslation($langcode)) {
@@ -124,13 +127,14 @@ protected function doTestOutdatedStatus() {
 
     // Mark translations as outdated.
     $edit = array('content_translation[retranslate]' => TRUE);
-    $this->drupalPostForm($langcode . '/' . $this->controller->getEditPath($entity), $edit, $this->getFormSubmitAction($entity));
+    $uri = $entity->uri('edit-form');
+    $this->drupalPostForm($langcode . '/' . $uri['path'], $edit, $this->getFormSubmitAction($entity));
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
 
     // Check that every translation has the correct "outdated" status.
     foreach ($this->langcodes as $enabled_langcode) {
       $prefix = $enabled_langcode != $default_langcode ? $enabled_langcode . '/' : '';
-      $path = $prefix . $this->controller->getEditPath($entity);
+      $path = $prefix . $uri['path'];
       $this->drupalGet($path);
       if ($enabled_langcode == $langcode) {
         $this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is not checked by default.');
@@ -152,7 +156,8 @@ protected function doTestOutdatedStatus() {
    */
   protected function doTestPublishedStatus() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
-    $path = $this->controller->getEditPath($entity);
+    $uri = $entity->uri('edit-form');
+    $path = $uri['path'];
 
     // Unpublish translations.
     foreach ($this->langcodes as $index => $langcode) {
@@ -174,7 +179,8 @@ protected function doTestPublishedStatus() {
    */
   protected function doTestAuthoringInfo() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
-    $path = $this->controller->getEditPath($entity);
+    $uri = $entity->uri('edit-form');
+    $path = $uri['path'];
     $values = array();
 
     // Post different authoring information for each translation.
@@ -218,7 +224,8 @@ protected function doTestTranslationDeletion() {
     // Confirm and delete a translation.
     $langcode = 'fr';
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
-    $this->drupalPostForm($langcode . '/' . $this->controller->getEditPath($entity), array(), t('Delete translation'));
+    $uri = $entity->uri('edit-form');
+    $this->drupalPostForm($langcode . '/' . $uri['path'], array(), t('Delete translation'));
     $this->drupalPostForm(NULL, array(), t('Delete'));
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
     if ($this->assertTrue(is_object($entity), 'Entity found')) {
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationWorkflowsTest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationWorkflowsTest.php
index c523900..0b3da77 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationWorkflowsTest.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationWorkflowsTest.php
@@ -67,7 +67,8 @@ protected function setupEntity() {
 
     // Create a translation.
     $this->drupalLogin($this->translator);
-    $add_translation_path = $this->controller->getBasePath($this->entity) . "/translations/add/$default_langcode/{$this->langcodes[2]}";
+    $uri = $this->entity->uri('drupal:content-translation-overview');
+    $add_translation_path = $uri['path'] . "/add/$default_langcode/{$this->langcodes[2]}";
     $this->drupalPostForm($add_translation_path, array(), t('Save'));
     $this->rebuildContainer();
   }
@@ -90,7 +91,8 @@ function testWorkflows() {
 
     // Check that translation permissions governate the associated operations.
     $ops = array('create' => t('Add'), 'update' => t('Edit'), 'delete' => t('Delete'));
-    $translations_path = $this->controller->getBasePath($this->entity) . "/translations";
+    $uri = $this->entity->uri('drupal:content-translation-overview');
+    $translations_path = $uri['path'];
     foreach ($ops as $current_op => $label) {
       $user = $this->drupalCreateUser(array($this->getTranslatePermission(), "$current_op content translations"));
       $this->drupalLogin($user);
@@ -123,14 +125,16 @@ protected function assertWorkflows(UserInterface $user, $expected_status) {
     $this->drupalLogin($user);
 
     // Check whether the user is allowed to access the entity form in edit mode.
-    $edit_path = $this->controller->getEditPath($this->entity);
+    $uri = $this->entity->uri('edit-form');
+    $edit_path = $uri['path'];
     $options = array('language' => $languages[$default_langcode]);
     $this->drupalGet($edit_path, $options);
     $this->assertResponse($expected_status['edit'], format_string('The @user_label has the expected edit access.', $args));
 
     // Check whether the user is allowed to access the translation overview.
     $langcode = $this->langcodes[1];
-    $translations_path = $this->controller->getBasePath($this->entity) . "/translations";
+    $uri = $this->entity->uri('drupal:content-translation-overview');
+    $translations_path = $uri['path'];
     $options = array('language' => $languages[$langcode]);
     $this->drupalGet($translations_path, $options);
     $this->assertResponse($expected_status['overview'], format_string('The @user_label has the expected translation overview access.', $args));
diff --git a/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php b/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php
index cef060c..a83ee6f 100644
--- a/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php
+++ b/core/modules/content_translation/tests/Drupal/content_translation/Tests/Menu/ContentTranslationLocalTasksTest.php
@@ -38,13 +38,10 @@ public function setUp() {
       ->method('getDefinitions')
       ->will($this->returnValue(array(
         'node' => array(
-          'translatable' => true,
-          'translation' => array(
-            'content_translation' => array(
-              // things.
-            ),
+          'translatable' => TRUE,
+          'links' => array(
+            'canonical' => '/node/{node}',
           ),
-          'menu_base_path' => 'node/%node',
         ),
       )));
     \Drupal::getContainer()->set('entity.manager', $entity_manager);
diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module
index 508af8a..120b754 100644
--- a/core/modules/entity_reference/entity_reference.module
+++ b/core/modules/entity_reference/entity_reference.module
@@ -24,12 +24,14 @@ function entity_reference_field_info_alter(&$info) {
   $info['entity_reference']['instance_settings']['handler_settings'] = array();
   $info['entity_reference']['default_widget'] = 'entity_reference_autocomplete';
   $info['entity_reference']['default_formatter'] = 'entity_reference_label';
+  $info['entity_reference']['list_class'] = '\Drupal\entity_reference\Plugin\Field\FieldType\ConfigurableEntityReferenceFieldItemList';
 }
 
 /**
  * Implements hook_entity_field_info_alter().
  *
- * Set the "target_type" property definition for entity reference fields.
+ * Set the "target_type" and "list_class" property definition for entity
+ * reference fields.
  *
  * @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::getPropertyDefinitions()
  *
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldType/ConfigurableEntityReferenceFieldItemList.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldType/ConfigurableEntityReferenceFieldItemList.php
new file mode 100644
index 0000000..5aa477a
--- /dev/null
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/Field/FieldType/ConfigurableEntityReferenceFieldItemList.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_reference\Plugin\Field\FieldType\ConfigurableEntityReferenceFieldItemList.
+ */
+
+namespace Drupal\entity_reference\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\ConfigFieldItemList;
+
+/**
+ * Represents a configurable entity_reference entity field.
+ */
+class ConfigurableEntityReferenceFieldItemList extends ConfigFieldItemList {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultValue() {
+    $default_value = parent::getDefaultValue();
+
+    if ($default_value) {
+      // Convert UUIDs to numeric IDs.
+      $uuids = array();
+      $fixed = array();
+      foreach ($default_value as $delta => $properties) {
+        if ($properties['target_uuid'] == 'anonymous' || $properties['target_uuid'] == 'administrator') {
+          $fixed[$delta] = ($properties['target_uuid'] == 'anonymous') ? '0' : '1';
+        }
+        else {
+          $uuids[$delta] = $properties['target_uuid'];
+        }
+      }
+      if ($uuids) {
+        $entities = \Drupal::entityManager()
+          ->getStorageController($this->getFieldDefinition()->getFieldSetting('target_type'))
+          ->loadByProperties(array('uuid' => $uuids));
+
+        foreach ($entities as $id => $entity) {
+          $entity_ids[$entity->uuid()] = $id;
+        }
+        foreach ($uuids as $delta => $uuid) {
+          if ($entity_ids[$uuid]) {
+            $default_value[$delta]['target_id'] = $entity_ids[$uuid];
+            unset($default_value[$delta]['target_uuid']);
+          }
+          else {
+            unset($default_value[$delta]);
+          }
+        }
+      }
+
+      if ($fixed) {
+        foreach ($fixed as $delta => $id) {
+          $default_value[$delta]['target_id'] = $id;
+          unset($default_value[$delta]['target_uuid']);
+        }
+      }
+
+      // Ensure we return consecutive deltas, in case we removed unknown UUIDs.
+      $default_value = array_values($default_value);
+    }
+    return $default_value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultValuesFormSubmit(array $element, array &$form, array &$form_state) {
+    $default_value = parent::defaultValuesFormSubmit($element, $form, $form_state);
+
+    // Convert numeric IDs to UUIDs to ensure config deployability.
+    $ids = array();
+    foreach ($default_value as $delta => $properties) {
+      $ids[] = $properties['target_id'];
+    }
+    $entities = \Drupal::entityManager()
+      ->getStorageController($this->getFieldDefinition()->getFieldSetting('target_type'))
+      ->loadMultiple($ids);
+
+    foreach ($default_value as $delta => $properties) {
+      $uuid = $entities[$properties['target_id']]->uuid();
+      // @todo Some entities do not have uuid. IE: Anonymous and admin user.
+      //   Remove this special case once http://drupal.org/node/2050843
+      //   has been fixed.
+      if (!$uuid) {
+        $uuid = ($properties['target_id'] == '0') ? 'anonymous' : 'administrator';
+      }
+      unset($default_value[$delta]['target_id']);
+      $default_value[$delta]['target_uuid'] = $uuid;
+    }
+    return $default_value;
+  }
+
+}
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFieldDefaultValueTest.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFieldDefaultValueTest.php
new file mode 100644
index 0000000..7d61f3d
--- /dev/null
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceFieldDefaultValueTest.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_reference\Tests\EntityReferenceFieldDefaultValueTest.
+ */
+
+namespace Drupal\entity_reference\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests entity reference field default values storage in CMI.
+ */
+class EntityReferenceFieldDefaultValueTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('entity_reference', 'field_ui', 'node', 'options');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Entity Reference field default value',
+      'description' => 'Tests the entity reference field default values storage in CMI.',
+      'group' => 'Entity Reference',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Create default content type.
+    $this->drupalCreateContentType(array('type' => 'reference_content'));
+    $this->drupalCreateContentType(array('type' => 'referenced_content'));
+
+    // Create admin user.
+    $this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'bypass node access'));
+    $this->drupalLogin($this->admin_user);
+  }
+
+  /**
+   * Tests that default values are correctly translated to UUIDs in config.
+   */
+  function testEntityReferenceDefaultValue() {
+    // Create a node to be referenced.
+    $referenced_node = $this->drupalCreateNode(array('type' => 'referenced_content'));
+
+    $this->field = entity_create('field_entity', array(
+      'name' => drupal_strtolower($this->randomName()),
+      'entity_type' => 'node',
+      'type' => 'entity_reference',
+      'settings' => array('target_type' => 'node'),
+    ));
+    $this->field->save();
+    $this->instance = entity_create('field_instance', array(
+      'field_name' => $this->field->name,
+      'entity_type' => 'node',
+      'bundle' => 'reference_content',
+      'settings' => array(
+        'handler' => 'default',
+        'handler_settings' => array(
+          'target_bundles' => array('referenced_content'),
+          'sort' => array('field' => '_none'),
+        ),
+      ),
+    ));
+    $this->instance->save();
+
+    // Set created node as default_value.
+    $instance_edit = array(
+      'default_value_input[' . $this->field->name . '][0][target_id]' => $referenced_node->getTitle() . ' (' .$referenced_node->id() . ')',
+    );
+    $this->drupalPostForm('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $this->field->name, $instance_edit, t('Save settings'));
+
+    // Check that default value is selected in default value form.
+    $this->drupalGet('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $this->field->name);
+    $this->assertRaw('name="default_value_input[' . $this->field->name . '][0][target_id]" value="' . $referenced_node->getTitle() .' (' .$referenced_node->id() . ')', 'The default value is selected in instance settings page');
+
+    // Check if the ID has been converted to UUID in config entity.
+    $config_entity = $this->container->get('config.factory')->get('field.instance.node.reference_content.' . $this->field->name)->get();
+    $this->assertTrue(isset($config_entity['default_value'][0]['target_uuid']), 'Default value contains target_uuid property');
+    $this->assertEqual($config_entity['default_value'][0]['target_uuid'], $referenced_node->uuid(), 'Content uuid and config entity uuid are the same');
+
+    // Clean field_info cache in order to avoid stale cache values.
+    field_info_cache_clear();
+
+    // Create a new node to check that UUID has been converted to numeric ID.
+    $new_node = entity_create('node', array('type' => 'reference_content'));
+    $this->assertEqual($new_node->get($this->field->name)->offsetGet(0)->target_id, $referenced_node->id());
+  }
+
+}
diff --git a/core/modules/entity_reference/tests/modules/entity_reference_test/config/views.view.test_entity_reference.yml b/core/modules/entity_reference/tests/modules/entity_reference_test/config/views.view.test_entity_reference.yml
index c3f265a..82c39d1 100644
--- a/core/modules/entity_reference/tests/modules/entity_reference_test/config/views.view.test_entity_reference.yml
+++ b/core/modules/entity_reference/tests/modules/entity_reference_test/config/views.view.test_entity_reference.yml
@@ -6,7 +6,7 @@ id: test_entity_reference
 description: ''
 label: 'Entity reference'
 tag: ''
-status: '1'
+status: true
 display:
   default:
     display_plugin: default
@@ -35,27 +35,27 @@ display:
           field: title
           label: ''
           alter:
-            alter_text: '0'
-            make_link: '0'
-            absolute: '0'
-            trim: '0'
-            word_boundary: '0'
-            ellipsis: '0'
-            strip_tags: '0'
-            html: '0'
-          hide_empty: '0'
-          empty_zero: '0'
-          link_to_node: '1'
+            alter_text: false
+            make_link: false
+            absolute: false
+            trim: false
+            word_boundary: false
+            ellipsis: false
+            strip_tags: false
+            html: false
+          hide_empty: false
+          empty_zero: false
+          link_to_node: true
           provider: node
       filters:
         status:
-          value: '1'
+          value: true
           table: node_field_data
           field: status
           id: status
           expose:
-            operator: '0'
-          group: '1'
+            operator: 0
+          group: true
           provider: views
       sorts:
         created:
@@ -79,4 +79,4 @@ display:
       pager:
         type: none
         options:
-          offset: '0'
+          offset: 0
diff --git a/core/modules/menu/menu.local_actions.yml b/core/modules/menu/menu.local_actions.yml
index a0016bc..cca55d8 100644
--- a/core/modules/menu/menu.local_actions.yml
+++ b/core/modules/menu/menu.local_actions.yml
@@ -3,3 +3,9 @@ menu_link_add:
   title: 'Add link'
   appears_on:
     - menu.menu_edit
+
+menu.menu_add:
+  route_name: menu.menu_add
+  title: 'Add menu'
+  appears_on:
+    - menu.overview_page
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index c48bb46..5fe0aa7 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -74,11 +74,6 @@ function menu_menu() {
     'title' => 'List menus',
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
-  $items['admin/structure/menu/add'] = array(
-    'title' => 'Add menu',
-    'route_name' => 'menu.menu_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
   $items['admin/structure/menu/settings'] = array(
     'title' => 'Settings',
     'route_name' => 'menu.settings',
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
index baa8d23..bab9996 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
@@ -74,7 +74,7 @@ protected function getFormSubmitAction(EntityInterface $entity) {
    */
   protected function doTestPublishedStatus() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
-    $path = $this->controller->getEditPath($entity);
+    $uri = $entity->uri('edit-form');
     $languages = language_list();
 
     $actions = array(
@@ -89,7 +89,7 @@ protected function doTestPublishedStatus() {
         if (!empty($status_actions)) {
           $action = array_shift($status_actions);
         }
-        $this->drupalPostForm($path, array(), $action, array('language' => $languages[$langcode]));
+        $this->drupalPostForm($uri['path'], array(), $action, array('language' => $languages[$langcode]));
       }
       $entity = entity_load($this->entityType, $this->entityId, TRUE);
       foreach ($this->langcodes as $langcode) {
@@ -106,7 +106,7 @@ protected function doTestPublishedStatus() {
    */
   protected function doTestAuthoringInfo() {
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
-    $path = $this->controller->getEditPath($entity);
+    $uri = $entity->uri('edit-form');
     $languages = language_list();
     $values = array();
 
@@ -122,7 +122,7 @@ protected function doTestAuthoringInfo() {
         'date[date]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d'),
         'date[time]' => format_date($values[$langcode]['created'], 'custom', 'H:i:s'),
       );
-      $this->drupalPostForm($path, $edit, $this->getFormSubmitAction($entity), array('language' => $languages[$langcode]));
+      $this->drupalPostForm($uri['path'], $edit, $this->getFormSubmitAction($entity), array('language' => $languages[$langcode]));
     }
 
     $entity = entity_load($this->entityType, $this->entityId, TRUE);
diff --git a/core/modules/node/node.local_actions.yml b/core/modules/node/node.local_actions.yml
new file mode 100644
index 0000000..feb2028
--- /dev/null
+++ b/core/modules/node/node.local_actions.yml
@@ -0,0 +1,5 @@
+node.type_add:
+  route_name: node.type_add
+  title: 'Add content type'
+  appears_on:
+    - node.overview_types
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 4bed03f..438104d 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -949,11 +949,6 @@ function node_menu() {
     'title' => 'List',
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
-  $items['admin/structure/types/add'] = array(
-    'title' => 'Add content type',
-    'route_name' => 'node.type_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
   $items['admin/structure/types/manage/%node_type'] = array(
     'title' => 'Edit content type',
     'title callback' => 'entity_page_label',
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
index b87bb76..c106689 100644
--- a/core/modules/node/node.routing.yml
+++ b/core/modules/node/node.routing.yml
@@ -23,7 +23,7 @@ node.page_edit:
 node.add_page:
   path: '/node/add'
   defaults:
-    _title: 'Add page'
+    _title: 'Add content'
     _content: '\Drupal\node\Controller\NodeController::addPage'
   options:
     _access_mode: 'ANY'
diff --git a/core/modules/path/path.local_actions.yml b/core/modules/path/path.local_actions.yml
new file mode 100644
index 0000000..9c58984
--- /dev/null
+++ b/core/modules/path/path.local_actions.yml
@@ -0,0 +1,5 @@
+path.admin_add:
+  route_name: path.admin_add
+  title: 'Add alias'
+  appears_on:
+    - path.admin_overview
diff --git a/core/modules/path/path.module b/core/modules/path/path.module
index 08d82d5..a965e9d 100644
--- a/core/modules/path/path.module
+++ b/core/modules/path/path.module
@@ -74,11 +74,6 @@ function path_menu() {
     'title' => 'Delete alias',
     'route_name' => 'path.delete',
   );
-  $items['admin/config/search/path/add'] = array(
-    'title' => 'Add alias',
-    'route_name' => 'path.admin_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
 
   return $items;
 }
diff --git a/core/modules/picture/picture.local_actions.yml b/core/modules/picture/picture.local_actions.yml
new file mode 100644
index 0000000..0fccbb2
--- /dev/null
+++ b/core/modules/picture/picture.local_actions.yml
@@ -0,0 +1,5 @@
+picture.mapping_page_add:
+  route_name: picture.mapping_page_add
+  title: 'Add picture mapping'
+  appears_on:
+    - picture.mapping_page
diff --git a/core/modules/picture/picture.module b/core/modules/picture/picture.module
index 2a6e6d2..b9b3b01 100644
--- a/core/modules/picture/picture.module
+++ b/core/modules/picture/picture.module
@@ -59,11 +59,6 @@ function picture_menu() {
     'weight' => 10,
     'route_name' => 'picture.mapping_page',
   );
-  $items['admin/config/media/picturemapping/add'] = array(
-    'title' => 'Add picture mapping',
-    'route_name' => 'picture.mapping_page_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
   $items['admin/config/media/picturemapping/%picture_mapping'] = array(
     'title' => 'Edit picture mapping',
     'route_name' => 'picture.mapping_page_edit',
diff --git a/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php b/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
index 2b08044..262d0a6 100644
--- a/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
+++ b/core/modules/serialization/lib/Drupal/serialization/Tests/EntitySerializationTest.php
@@ -34,7 +34,7 @@ class EntitySerializationTest extends NormalizerTestBase {
   protected $entity;
 
   /**
-   * The serializer serivce.
+   * The serializer service.
    *
    * @var \Symfony\Component\Serializer\Serializer.
    */
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Controller/ShortcutSetController.php b/core/modules/shortcut/lib/Drupal/shortcut/Controller/ShortcutSetController.php
index 7123ae5..d2f38eb 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Controller/ShortcutSetController.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Controller/ShortcutSetController.php
@@ -33,9 +33,8 @@ class ShortcutSetController extends ControllerBase {
    * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
    */
   public function addShortcutLinkInline(ShortcutSetInterface $shortcut_set, Request $request) {
-    $token = $request->query->get('token');
     $link = $request->query->get('link');
-    if (isset($token) && drupal_valid_token($token, 'shortcut-add-link') && shortcut_valid_link($link)) {
+    if (shortcut_valid_link($link)) {
       $item = menu_get_item($link);
       $title = ($item && $item['title']) ? $item['title'] : $link;
       $link = array(
diff --git a/core/modules/shortcut/shortcut.local_actions.yml b/core/modules/shortcut/shortcut.local_actions.yml
index 2067f7d..3ed4a75 100644
--- a/core/modules/shortcut/shortcut.local_actions.yml
+++ b/core/modules/shortcut/shortcut.local_actions.yml
@@ -3,3 +3,8 @@ shortcut_set_add_local_action:
   title: 'Add shortcut set'
   appears_on:
     - shortcut.set_admin
+shortcut.link_add:
+  route_name: shortcut.link_add
+  title: 'Add shortcut'
+  appears_on:
+    - shortcut.set_customize
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index dc9bd86..5579d55 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -106,11 +106,6 @@ function shortcut_menu() {
     'type' => MENU_LOCAL_TASK,
     'weight' => 10,
   );
-  $items['admin/config/user-interface/shortcut/manage/%shortcut_set/add-link'] = array(
-    'title' => 'Add shortcut',
-    'route_name' => 'shortcut.link_edit',
-    'type' => MENU_LOCAL_ACTION,
-  );
   $items['admin/config/user-interface/shortcut/link/%menu_link'] = array(
     'title' => 'Edit shortcut',
     'route_name' => 'shortcut.link_edit',
@@ -456,14 +451,15 @@ function shortcut_preprocess_page(&$variables) {
     $link_mode = isset($mlid) ? "remove" : "add";
 
     if ($link_mode == "add") {
-      $query['token'] = drupal_get_token('shortcut-add-link');
       $link_text = shortcut_set_switch_access() ? t('Add to %shortcut_set shortcuts', array('%shortcut_set' => $shortcut_set->label())) : t('Add to shortcuts');
-      $link_path = 'admin/config/user-interface/shortcut/manage/' . $shortcut_set->id() . '/add-link-inline';
+      $route_name = 'shortcut.link_add_inline';
+      $route_parameters = array('shortcut_set' => $shortcut_set->id());
     }
     else {
       $query['mlid'] = $mlid;
       $link_text = shortcut_set_switch_access() ? t('Remove from %shortcut_set shortcuts', array('%shortcut_set' => $shortcut_set->label())) : t('Remove from shortcuts');
-      $link_path = 'admin/config/user-interface/shortcut/link/' . $mlid . '/delete';
+      $route_name = 'shortcut.link_delete';
+      $route_parameters = array('menu_link' => $mlid);
     }
 
     if (theme_get_setting('shortcut_module_link')) {
@@ -476,7 +472,8 @@ function shortcut_preprocess_page(&$variables) {
         '#prefix' => '<div class="add-or-remove-shortcuts ' . $link_mode . '-shortcut">',
         '#type' => 'link',
         '#title' => '<span class="icon">'. t('Add or remove shortcut') .'</span><span class="text">' . $link_text . '</span>',
-        '#href' => $link_path,
+        '#route_name' => $route_name,
+        '#route_parameters' => $route_parameters,
         '#options' => array('query' => $query, 'html' => TRUE),
         '#suffix' => '</div>',
       );
diff --git a/core/modules/shortcut/shortcut.routing.yml b/core/modules/shortcut/shortcut.routing.yml
index 73decdd..34dbac1 100644
--- a/core/modules/shortcut/shortcut.routing.yml
+++ b/core/modules/shortcut/shortcut.routing.yml
@@ -40,6 +40,7 @@ shortcut.link_add_inline:
     _controller: 'Drupal\shortcut\Controller\ShortcutSetController::addShortcutLinkInline'
   requirements:
     _entity_access: 'shortcut_set.update'
+    _csrf_token: 'shortcut-add-link'
 
 shortcut.set_customize:
   path: '/admin/config/user-interface/shortcut/manage/{shortcut_set}'
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemHelpBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemHelpBlock.php
index a8c05fe..f7b389d 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemHelpBlock.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemHelpBlock.php
@@ -92,13 +92,13 @@ public function access(AccountInterface $account) {
    */
   protected function getActiveHelp(Request $request) {
     $output = '';
-    $router_path = menu_tab_root_path();
+    $router_path = $request->attributes->get('_system_path');
     // We will always have a path unless we are on a 403 or 404.
     if (!$router_path) {
       return '';
     }
 
-    $arg = drupal_help_arg(explode('/', $request->attributes->get('_system_path')));
+    $arg = drupal_help_arg(explode('/', $router_path));
 
     foreach ($this->moduleHandler->getImplementations('help') as $module) {
       $function = $module . '_help';
diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
index 9849cb3..3d97b36 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/DialogTest.php
@@ -68,7 +68,7 @@ public function testDialog() {
       'settings' => NULL,
       'dialogOptions' => array(
         'modal' => TRUE,
-        'title' => 'Home',
+        'title' => 'Add category',
       ),
     );
     $normal_expected_response = array(
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/LocalActionTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/LocalActionTest.php
index de61cb8..d8804dd 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/LocalActionTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/LocalActionTest.php
@@ -37,8 +37,8 @@ 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/hook_menu' => 'My hook_menu action',
       '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',
     ));
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
index 4b69e98..71cf0c4 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
@@ -232,10 +232,6 @@ protected function doTestMenuHidden() {
     $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
     $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
 
-    $link = $links['menu-test/hidden/menu/add'];
-    $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
-    $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
-
     $link = $links['menu-test/hidden/menu/settings'];
     $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
     $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
@@ -252,10 +248,6 @@ protected function doTestMenuHidden() {
     $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
     $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
 
-    $link = $links['menu-test/hidden/menu/manage/%/add'];
-    $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
-    $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
-
     $link = $links['menu-test/hidden/menu/manage/%/edit'];
     $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
     $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
@@ -284,10 +276,6 @@ protected function doTestMenuHidden() {
     $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
     $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
 
-    $link = $links['menu-test/hidden/block/add'];
-    $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
-    $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
-
     $link = $links['menu-test/hidden/block/manage/%/%'];
     $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth)));
     $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid)));
diff --git a/core/modules/system/system.local_actions.yml b/core/modules/system/system.local_actions.yml
new file mode 100644
index 0000000..c08a2cf
--- /dev/null
+++ b/core/modules/system/system.local_actions.yml
@@ -0,0 +1,6 @@
+system.date_format_add:
+  route_name: system.date_format_add
+  title: 'Add format'
+  weight: -10
+  appears_on:
+    - system.date_format_list
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 66e5a6f..9e2231b 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -736,13 +736,6 @@ function system_menu() {
     'route_name' => 'system.date_format_list',
     'weight' => -9,
   );
-  $items['admin/config/regional/date-time/formats/add'] = array(
-    'title' => 'Add format',
-    'description' => 'Allow users to add additional date formats.',
-    'type' => MENU_LOCAL_ACTION,
-    'route_name' => 'system.date_format_add',
-    'weight' => -10,
-  );
   $items['admin/config/regional/date-time/formats/manage/%'] = array(
     'title' => 'Edit date format',
     'description' => 'Allow users to edit a configured date format.',
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index c8f868e..2348856 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -62,9 +62,6 @@ function entity_test_entity_types($filter = NULL) {
  */
 function entity_test_entity_info_alter(&$info) {
   foreach (entity_test_entity_types() as $entity_type) {
-    // Ensure these test entity types use their base path as their edit path.
-    $info[$entity_type]['menu_edit_path'] = $info[$entity_type]['menu_base_path'];
-
     // Optionally specify a translation handler for testing translations.
     if (\Drupal::state()->get('entity_test.translation')) {
       $info[$entity_type]['translation'][$entity_type] = TRUE;
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
index 69f1aab..d776c85 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
@@ -37,7 +37,6 @@
  *     "bundle" = "type",
  *     "label" = "name"
  *   },
- *   menu_base_path = "entity_test/manage/%entity_test",
  *   route_base_path = "admin/structure/entity-test/manage/{bundle}",
  *   links = {
  *     "canonical" = "/entity_test/{entity_test}",
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestCache.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestCache.php
index 55a7b5e..48d345e 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestCache.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestCache.php
@@ -32,8 +32,7 @@
  *     "id" = "id",
  *     "uuid" = "uuid",
  *     "bundle" = "type"
- *   },
- *   menu_base_path = "entity-test/manage/%entity_test"
+ *   }
  * )
  */
 class EntityTestCache extends EntityTest {
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php
index 7907322..e4e0f9c 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMul.php
@@ -36,8 +36,11 @@
  *     "bundle" = "type",
  *     "label" = "name"
  *   },
- *   menu_base_path = "entity_test_mul/manage/%entity_test_mul",
- *   route_base_path = "entity_test_mul/structure/{bundle}"
+ *   route_base_path = "entity_test_mul/structure/{bundle}",
+ *   links = {
+ *     "canonical" = "/entity_test_mul/manage/{entity_test_mul}",
+ *     "edit-form" = "/entity_test_mul/manage/{entity_test_mul}"
+ *   }
  * )
  */
 class EntityTestMul extends EntityTest {
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMulRev.php
index 3d8df50..25887a5 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestMulRev.php
@@ -37,7 +37,10 @@
  *     "revision" = "revision_id",
  *     "bundle" = "type"
  *   },
- *   menu_base_path = "entity_test_mulrev/manage/%entity_test_mulrev"
+ *   links = {
+ *     "canonical" = "/entity_test_mulrev/manage/{entity_test_mulrev}",
+ *     "edit-form" = "/entity_test_mulrev/manage/{entity_test_mulrev}"
+ *   }
  * )
  */
 class EntityTestMulRev extends EntityTestRev {
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestRev.php
index 19760e8..fb6d42c 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestRev.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestRev.php
@@ -34,7 +34,10 @@
  *     "revision" = "revision_id",
  *     "bundle" = "type"
  *   },
- *   menu_base_path = "entity_test_rev/manage/%entity_test_rev"
+ *   links = {
+ *     "canonical" = "/entity_test_rev/manage/{entity_test_rev}",
+ *     "edit-form" = "/entity_test_rev/manage/{entity_test_rev}"
+ *   }
  * )
  */
 class EntityTestRev extends EntityTest {
diff --git a/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalAction/TestLocalAction4.php b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalAction/TestLocalAction4.php
new file mode 100644
index 0000000..3f9a44e
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/lib/Drupal/menu_test/Plugin/Menu/LocalAction/TestLocalAction4.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_test\Plugin\Menu\LocalAction\TestLocalAction4.
+ */
+
+namespace Drupal\menu_test\Plugin\Menu\LocalAction;
+
+use Drupal\Core\Menu\LocalActionDefault;
+
+/**
+ * Defines a local action plugin with a dynamic title.
+ */
+class TestLocalAction4 extends LocalActionDefault {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->t('My @arg action', array('@arg' => 'dynamic-title'));
+  }
+
+}
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.local_actions.yml b/core/modules/system/tests/modules/menu_test/menu_test.local_actions.yml
index 76bde78..24c92a2 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.local_actions.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.local_actions.yml
@@ -11,3 +11,35 @@ menu_test.local_action5:
   appears_on:
     - menu_test.local_action1
 
+menu_test.local_action2:
+  route_name: menu_test.local_action2
+  title: 'My hook_menu action'
+  weight: -10
+  appears_on:
+    - menu_test.local_action1
+
+menu_test.local_action4:
+  route_name: menu_test.local_action4
+  title: 'My dynamic title action'
+  weight: -20
+  class: '\Drupal\menu_test\Plugin\Menu\LocalAction\TestLocalAction4'
+  appears_on:
+    - menu_test.local_action1
+
+menu_test.hidden_menu_add:
+  route_name: menu_test.hidden_menu_add
+  title: 'Add menu'
+  appears_on:
+    - menu_test.hidden_menu
+
+menu_test.hidden_manage_add:
+  route_name: menu_test.hidden_manage_add
+  title: 'Add link'
+  appears_on:
+    - menu_test.hidden_manage
+
+menu_test.hidden_block_add:
+  route_name: menu_test.hidden_block_add
+  title: 'Add block'
+  appears_on:
+    - menu_test.hidden_block
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.module b/core/modules/system/tests/modules/menu_test/menu_test.module
index c42e705..ab2ef30 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.module
+++ b/core/modules/system/tests/modules/menu_test/menu_test.module
@@ -108,11 +108,6 @@ function menu_test_menu() {
     'title' => 'List menus',
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
-  $items['menu-test/hidden/menu/add'] = array(
-    'title' => 'Add menu',
-    'route_name' => 'menu_test.hidden_menu_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
   $items['menu-test/hidden/menu/settings'] = array(
     'title' => 'Settings',
     'route_name' => 'menu_test.hidden_menu_settings',
@@ -128,11 +123,6 @@ function menu_test_menu() {
     'type' => MENU_DEFAULT_LOCAL_TASK,
     'context' => MENU_CONTEXT_PAGE,
   );
-  $items['menu-test/hidden/menu/manage/%menu/add'] = array(
-    'title' => 'Add link',
-    'route_name' => 'menu_test.hidden_manage_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
   $items['menu-test/hidden/menu/manage/%menu/edit'] = array(
     'title' => 'Edit menu',
     'route_name' => 'menu_test.hidden_manage_edit',
@@ -153,11 +143,6 @@ function menu_test_menu() {
     'title' => 'List',
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
-  $items['menu-test/hidden/block/add'] = array(
-    'title' => 'Add block',
-    'route_name' => 'menu_test.hidden_block_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
   $items['menu-test/hidden/block/manage/%/%'] = array(
     'title' => 'Configure block',
     'route_name' => 'menu_test.hidden_block_configure',
@@ -298,22 +283,6 @@ function menu_test_menu() {
     'route_name' => 'menu_test.local_action1',
   );
 
-  $items['menu-test-local-action/hook_menu'] = array(
-    'title' => 'My hook_menu action',
-    'route_name' => 'menu_test.local_action2',
-    'weight' => -10,
-    'type' => MENU_LOCAL_ACTION,
-  );
-
-  $items['menu-test-local-action/dynamic-title'] = array(
-    'title' => 'My dynamic title action',
-    'title callback' => 'menu_test_local_action_dynamic_title',
-    'title arguments' => array(1),
-    'route_name' => 'menu_test.local_action4',
-    'weight' => -10,
-    'type' => MENU_LOCAL_ACTION,
-  );
-
   $items['menu-local-task-test/tasks'] = array(
     'title' => 'Local tasks',
     'route_name' => 'menu_test.local_task_test_tasks',
@@ -328,13 +297,6 @@ function menu_test_menu() {
 }
 
 /**
- * Title callback: Set a dynamic title for a local action.
- */
-function menu_test_local_action_dynamic_title($arg) {
-  return t('My @arg action', array('@arg' => $arg));
-}
-
-/**
  * Implements hook_menu_local_tasks().
  *
  * If the menu_test.settings configuration 'tasks.add' has been set, adds
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
index 033c5f1..7c9e9c3 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
@@ -48,7 +48,6 @@
  *     "canonical" = "/taxonomy/term/{taxonomy_term}",
  *     "edit-form" = "/taxonomy/term/{taxonomy_term}/edit"
  *   },
- *   menu_base_path = "taxonomy/term/%taxonomy_term",
  *   route_base_path = "admin/structure/taxonomy/manage/{bundle}",
  *   permission_granularity = "bundle"
  * )
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Field/FieldType/TaxonomyTermReferenceFieldItemList.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Field/FieldType/TaxonomyTermReferenceFieldItemList.php
new file mode 100644
index 0000000..096a452
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Field/FieldType/TaxonomyTermReferenceFieldItemList.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\Plugin\Field\FieldType\TaxonomyTermReferenceFieldItemList.
+ */
+
+namespace Drupal\taxonomy\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\Plugin\Field\FieldType\LegacyConfigFieldItemList;
+
+/**
+ * Represents a configurable taxonomy_term_reference entity field.
+ */
+class TaxonomyTermReferenceFieldItemList extends LegacyConfigFieldItemList {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultValue() {
+    $default_value = parent::getDefaultValue();
+
+    if ($default_value) {
+      // Convert UUIDs to numeric IDs.
+      $uuids = array();
+      foreach ($default_value as $delta => $properties) {
+        $uuids[$delta] = $properties['target_uuid'];
+      }
+      if ($uuids) {
+        $entities = \Drupal::entityManager()
+          ->getStorageController('taxonomy_term')
+          ->loadByProperties(array('uuid' => $uuids));
+
+        foreach ($entities as $id => $entity) {
+          $entity_ids[$entity->uuid()] = $id;
+        }
+        foreach ($uuids as $delta => $uuid) {
+          if (isset($entity_ids[$uuid])) {
+            $default_value[$delta]['target_id'] = $entity_ids[$uuid];
+            unset($default_value[$delta]['target_uuid']);
+          }
+          else {
+            unset($default_value[$delta]);
+          }
+        }
+      }
+
+      // Ensure we return consecutive deltas, in case we removed unknown UUIDs.
+      $default_value = array_values($default_value);
+    }
+    return $default_value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultValuesFormSubmit(array $element, array &$form, array &$form_state) {
+    $default_value = parent::defaultValuesFormSubmit($element, $form, $form_state);
+
+    // Convert numeric IDs to UUIDs to ensure config deployability.
+    $ids = array();
+    foreach ($default_value as $delta => $properties) {
+      $ids[] = $properties['target_id'];
+    }
+    $entities = \Drupal::entityManager()
+      ->getStorageController('taxonomy_term')
+      ->loadMultiple($ids);
+
+    foreach ($default_value as $delta => $properties) {
+      unset($default_value[$delta]['target_id']);
+      $default_value[$delta]['target_uuid'] = $entities[$properties['target_id']]->uuid();
+    }
+    return $default_value;
+  }
+
+}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermTranslationController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermTranslationController.php
index f7c704a..ba951a1 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermTranslationController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermTranslationController.php
@@ -36,7 +36,8 @@ function entityFormSave(array $form, array &$form_state) {
       // We need a redirect here, otherwise we would get an access denied page,
       // since the current URL would be preserved and we would try to add a
       // translation for a language that already has a translation.
-      $form_state['redirect'] = $this->getEditPath($entity);
+      $uri = $entity->uri('edit-form');
+      $form_state['redirect'] = $uri['path'];
     }
   }
 
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyUpgradePathTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyUpgradePathTest.php
new file mode 100644
index 0000000..b288636
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TaxonomyUpgradePathTest.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\Tests\TaxonomyUpgradePathTest.
+ */
+
+namespace Drupal\taxonomy\Tests;
+
+use Drupal\system\Tests\Upgrade\UpgradePathTestBase;
+
+/**
+ * Tests upgrade of taxonomy variables.
+ */
+class TaxonomyUpgradePathTest extends UpgradePathTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Taxonomy upgrade test',
+      'description' => 'Tests upgrade of Taxonomy module.',
+      'group' => 'Upgrade path',
+    );
+  }
+
+  public function setUp() {
+    $this->databaseDumpFiles = array(
+      drupal_get_path('module', 'system') . '/tests/upgrade/drupal-7.bare.standard_all.database.php.gz',
+      drupal_get_path('module', 'taxonomy') . '/tests/upgrade/drupal-7.taxonomy.database.php',
+    );
+    parent::setUp();
+  }
+
+  /**
+   * Tests upgrade of taxonomy_term_reference field default values.
+   */
+  public function testEntityDisplayUpgrade() {
+    $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
+
+    // Check that the configuration entries were created.
+    $config_entity = \Drupal::config('field.instance.node.article.field_tags')->get();
+    $this->assertTrue(!empty($config_entity), 'Config entity has been created');
+    $this->assertTrue(!empty($config_entity['default_value'][0]['target_uuid']), 'Default value contains target_uuid property');
+
+    // Load taxonomy term to check UUID conversion.
+    $taxonomy_term = entity_load('taxonomy_term', 2);
+
+    // Check that default_value has been converted to Drupal 8 structure.
+    $this->assertEqual($taxonomy_term->uuid(), $config_entity['default_value'][0]['target_uuid'], 'Default value contains the right target_uuid');
+    $this->assertEqual('', $config_entity['default_value'][0]['revision_id'], 'Default value contains the right revision_id');
+  }
+
+}
diff --git a/core/modules/taxonomy/taxonomy.install b/core/modules/taxonomy/taxonomy.install
index 845a44e..086c030 100644
--- a/core/modules/taxonomy/taxonomy.install
+++ b/core/modules/taxonomy/taxonomy.install
@@ -396,3 +396,35 @@ function taxonomy_update_8008() {
   );
   db_add_field('taxonomy_term_data', 'changed', $spec);
 }
+
+/**
+ * Convert numeric IDs to UUIDs to ensure config deployability.
+ */
+function taxonomy_update_8009() {
+  foreach (config_get_storage_names_with_prefix('field.instance.') as $instance_config_name) {
+    $instance_config = \Drupal::config($instance_config_name);
+    if ($instance_config->get('field_type') == 'taxonomy_term_reference') {
+      $default_value = $instance_config->get('default_value');
+      $ids = array();
+      // Load all referenced taxonomy_terms in default_value.
+      foreach ($default_value as $delta => $value) {
+        $ids[] = $value['tid'];
+      }
+      if ($ids) {
+        $entities = db_select('taxonomy_term_data', 't')
+          ->fields('t', array('tid', 'uuid'))
+          ->condition('t.tid', $ids)
+          ->execute()->fetchAllAssoc('tid');
+
+        // Convert IDs to UUIDs and save the new default_value.
+        foreach ($default_value as $delta => &$value) {
+          $value = array(
+            'target_uuid' => $entities[$value['tid']]->uuid,
+            'revision_id' => '',
+          );
+        }
+        $instance_config->set('default_value', $default_value)->save();
+      }
+    }
+  }
+}
diff --git a/core/modules/taxonomy/taxonomy.local_actions.yml b/core/modules/taxonomy/taxonomy.local_actions.yml
index 4e86539..689754a 100644
--- a/core/modules/taxonomy/taxonomy.local_actions.yml
+++ b/core/modules/taxonomy/taxonomy.local_actions.yml
@@ -3,3 +3,9 @@ taxonomy_add_vocabulary_local_action:
   title: 'Add vocabulary'
   appears_on:
     - taxonomy.vocabulary_list
+
+taxonomy.term_add:
+  route_name: taxonomy.term_add
+  title: 'Add term'
+  appears_on:
+    - taxonomy.overview_terms
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index acab699..72222aa 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -278,12 +278,6 @@ function taxonomy_menu() {
     'type' => MENU_LOCAL_TASK,
   );
 
-  $items['admin/structure/taxonomy/manage/%taxonomy_vocabulary/add'] = array(
-    'title' => 'Add term',
-    'route_name' => 'taxonomy.term_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
-
   return $items;
 }
 
@@ -851,6 +845,7 @@ function taxonomy_field_info() {
           ),
         ),
       ),
+      'list_class' => '\Drupal\taxonomy\Plugin\Field\FieldType\TaxonomyTermReferenceFieldItemList',
     ),
   );
 }
diff --git a/core/modules/taxonomy/tests/upgrade/drupal-7.taxonomy.database.php b/core/modules/taxonomy/tests/upgrade/drupal-7.taxonomy.database.php
new file mode 100644
index 0000000..6c729f0
--- /dev/null
+++ b/core/modules/taxonomy/tests/upgrade/drupal-7.taxonomy.database.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Database additions for taxonomy variables. Used in TaxonomyUpgradePathTest.
+ *
+ * The drupal-7.bare.standard_all.php file is imported before this dump, so the
+ * two form the database structure expected in tests altogether.
+ */
+
+// Add a new taxonomy_term in taxonomy_term_data table.
+db_insert('taxonomy_term_data')
+  ->fields(array(
+    'tid',
+    'vid',
+    'name',
+    'description',
+    'format',
+    'weight',
+  ))
+  ->values(array(
+    'tid' => '2',
+    'vid' => '1',
+    'name' => 'Default term',
+    'description' => '',
+    'format' => NULL,
+    'weight' => '0',
+  ))
+  ->execute();
+
+// Add a new taxonomy_term in taxonomy_term_hierarchy table.
+db_insert('taxonomy_term_hierarchy')
+  ->fields(array(
+    'tid',
+    'parent',
+  ))
+  ->values(array(
+    'tid' => '2',
+    'parent' => '0',
+  ))
+  ->execute();
+
+// Set the taxonomy_term added as default value for field_tags instance.
+db_update('field_config_instance')
+  ->fields(array(
+    'data' => 'a:7:{s:5:"label";s:4:"Tags";s:11:"description";s:63:"Enter a comma-separated list of words to describe your content.";s:6:"widget";a:5:{s:6:"weight";i:-4;s:4:"type";s:21:"taxonomy_autocomplete";s:6:"module";s:8:"taxonomy";s:6:"active";i:0;s:8:"settings";a:2:{s:4:"size";i:60;s:17:"autocomplete_path";s:21:"taxonomy/autocomplete";}}s:7:"display";a:2:{s:7:"default";a:5:{s:4:"type";s:28:"taxonomy_term_reference_link";s:6:"weight";i:10;s:5:"label";s:5:"above";s:8:"settings";a:0:{}s:6:"module";s:8:"taxonomy";}s:6:"teaser";a:5:{s:4:"type";s:28:"taxonomy_term_reference_link";s:6:"weight";i:10;s:5:"label";s:5:"above";s:8:"settings";a:0:{}s:6:"module";s:8:"taxonomy";}}s:8:"settings";a:1:{s:18:"user_register_form";b:0;}s:8:"required";i:0;s:13:"default_value";a:1:{i:0;a:8:{s:3:"tid";s:1:"2";s:3:"vid";s:1:"1";s:4:"name";s:12:"Default term";s:11:"description";s:0:"";s:6:"format";s:13:"filtered_html";s:6:"weight";s:1:"0";s:23:"vocabulary_machine_name";s:4:"tags";s:11:"rdf_mapping";a:5:{s:7:"rdftype";a:1:{i:0;s:12:"skos:Concept";}s:4:"name";a:1:{s:10:"predicates";a:2:{i:0;s:10:"rdfs:label";i:1;s:14:"skos:prefLabel";}}s:11:"description";a:1:{s:10:"predicates";a:1:{i:0;s:15:"skos:definition";}}s:3:"vid";a:2:{s:10:"predicates";a:1:{i:0;s:13:"skos:inScheme";}s:4:"type";s:3:"rel";}s:6:"parent";a:2:{s:10:"predicates";a:1:{i:0;s:12:"skos:broader";}s:4:"type";s:3:"rel";}}}}}')
+  )
+  ->condition('field_name', 'field_tags')
+  ->execute();
diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php
index f0c55be..7928455 100644
--- a/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php
+++ b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php
@@ -242,4 +242,25 @@ protected function setSystemInfo7_0() {
     );
     \Drupal::config('update_test.settings')->set('system_info', $setting)->save();
   }
+
+  /**
+   * Ensures that the local actions appear.
+   */
+  public function testLocalActions() {
+    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer modules', 'administer software updates', 'administer themes'));
+    $this->drupalLogin($admin_user);
+
+    $this->drupalGet('admin/modules');
+    $this->clickLink(t('Install new module'));
+    $this->assertUrl('admin/modules/install');
+
+    $this->drupalGet('admin/appearance');
+    $this->clickLink(t('Install new theme'));
+    $this->assertUrl('admin/theme/install');
+
+    $this->drupalGet('admin/reports/updates');
+    $this->clickLink(t('Install new module or theme'));
+    $this->assertUrl('admin/reports/updates/install');
+  }
+
 }
diff --git a/core/modules/update/update.local_actions.yml b/core/modules/update/update.local_actions.yml
new file mode 100644
index 0000000..d6f6b8d
--- /dev/null
+++ b/core/modules/update/update.local_actions.yml
@@ -0,0 +1,20 @@
+update.report_install:
+  route_name: update.report_install
+  title: 'Install new module or theme'
+  weight: 25
+  appears_on:
+    - update.status
+
+update.module_install:
+  route_name: update.module_install
+  title: 'Install new module'
+  weight: 25
+  appears_on:
+    - system.modules_list
+
+update.theme_install:
+  route_name: update.theme_install
+  title: 'Install new theme'
+  weight: 25
+  appears_on:
+    - system.themes_page
diff --git a/core/modules/update/update.module b/core/modules/update/update.module
index 4b9b67d..4cf9c0d 100644
--- a/core/modules/update/update.module
+++ b/core/modules/update/update.module
@@ -157,30 +157,6 @@ function update_menu() {
     'weight' => -50,
   );
 
-  // We want action links for updating projects at a few different locations:
-  // both the module and theme administration pages, and on the available
-  // updates report itself. The menu items will be mostly identical, except the
-  // paths and titles, so we just define them in a loop. We pass in a string
-  // indicating what context we're entering the action from, so that can
-  // customize the appearance as needed.
-  $paths = array(
-    'report' => 'admin/reports/updates',
-    'module' => 'admin/modules',
-    'theme' => 'admin/appearance',
-  );
-  foreach ($paths as $context => $path) {
-    $items[$path . '/install'] = array(
-      'route_name' => "update.{$context}_install",
-      'weight' => 25,
-      'type' => MENU_LOCAL_ACTION,
-    );
-  }
-  // Customize the titles of the action links depending on where they appear.
-  // We use += array() to let the translation extractor find these menu titles.
-  $items['admin/reports/updates/install'] += array('title' => 'Install new module or theme');
-  $items['admin/modules/install'] += array('title' => 'Install new module');
-  $items['admin/appearance/install'] += array('title' => 'Install new theme');
-
   return $items;
 }
 
diff --git a/core/modules/user/lib/Drupal/user/ProfileTranslationController.php b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
index 499f28c..9667170 100644
--- a/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
+++ b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
@@ -36,7 +36,8 @@ function entityFormSave(array $form, array &$form_state) {
       // We need a redirect here, otherwise we would get an access denied page
       // since the current URL would be preserved and we would try to add a
       // translation for a language that already has a translation.
-      $form_state['redirect'] = $this->getViewPath($entity);
+      $uri = $entity->uri();
+      $form_state['redirect'] = $uri['path'];
     }
   }
 }
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php
index d418eef..fa88502 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php
@@ -15,11 +15,14 @@
 class UserPictureTest extends WebTestBase {
 
   /**
-   * Modules to enable.
+   * The profile to install as a basis for testing.
    *
-   * @var array
+   * Using the standard profile to test user picture config provided by the
+   * standard profile.
+   *
+   * @var string
    */
-  public static $modules = array('image', 'comment');
+  protected $profile = 'standard';
 
   protected $user;
   protected $_directory_test;
@@ -41,12 +44,6 @@ function setUp() {
       'post comments',
       'skip comment approval',
     ));
-    $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
-    $this->container->get('comment.manager')->addDefaultField('node', 'article');
-
-    // @see standard.install
-    module_load_install('user');
-    user_install_picture_field();
   }
 
   /**
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index 9d25ccc..8f6cbe2 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Language\Language;
+use Drupal\field\Field;
 
 /**
  * Implements hook_schema().
@@ -256,97 +257,6 @@ function user_install() {
 }
 
 /**
- * Creates a user picture image field for the User entity.
- *
- * This is only used in core's standard.install, but is kept as a separate
- * helper function so that other install profiles can reuse it.
- */
-function user_install_picture_field() {
-  $field = array(
-    'name' => 'user_picture',
-    'entity_type' => 'user',
-    'module' => 'image',
-    'type' => 'image',
-    'cardinality' => 1,
-    'locked' => FALSE,
-    'indexes' => array('target_id' => array('target_id')),
-    'settings' => array(
-      'uri_scheme' => 'public',
-      'default_image' => array(
-        'fid' => NULL,
-        'alt' => '',
-        'title' => '',
-        'width' => NULL,
-        'height' => NULL,
-      ),
-    ),
-  );
-  entity_create('field_entity', $field)->save();
-
-  $instance = array(
-    'field_name' => 'user_picture',
-    'entity_type' => 'user',
-    'label' => 'Picture',
-    'bundle' => 'user',
-    'description' => t('Your virtual face or picture.'),
-    'required' => FALSE,
-    'settings' => array(
-      'file_extensions' => 'png gif jpg jpeg',
-      'file_directory' => 'pictures',
-      'max_filesize' => '30 KB',
-      'alt_field' => 0,
-      'title_field' => 0,
-      'max_resolution' => '85x85',
-      'min_resolution' => '',
-      'default_image' => array(
-        'fid' => NULL,
-        'alt' => '',
-        'title' => '',
-        'width' => NULL,
-        'height' => NULL,
-      ),
-    ),
-  );
-  entity_create('field_instance', $instance)->save();
-
-  // Assign form display settings for the 'default' view mode.
-  entity_get_form_display('user', 'user', 'default')
-    ->setComponent('user_picture', array(
-      'type' => 'image_image',
-      'settings' => array(
-        'progress_indicator' => 'throbber',
-        'preview_image_style' => 'thumbnail',
-      ),
-      'weight' => -1,
-    ))
-    ->save();
-
-  // Assign display settings for the 'default' and 'compact' view modes.
-  entity_get_display('user', 'user', 'default')
-    ->setComponent('user_picture', array(
-      'label' => 'hidden',
-      'type' => 'image',
-      'settings' => array(
-        'image_style' => 'thumbnail',
-        'image_link' => 'content',
-      ),
-    ))
-    ->save();
-  entity_get_display('user', 'user', 'compact')
-    ->setComponent('user_picture', array(
-      'label' => 'hidden',
-      'type' => 'image',
-      'settings' => array(
-        'image_style' => 'thumbnail',
-        'image_link' => 'content',
-      ),
-    ))
-    // Additionally, hide 'summary' pseudo-field from compact view mode..
-    ->removeComponent('member_for')
-    ->save();
-}
-
-/**
  * Implements hook_update_dependencies().
  */
 function user_update_dependencies() {
diff --git a/core/modules/user/user.local_actions.yml b/core/modules/user/user.local_actions.yml
index 9a57772..2a1f892 100644
--- a/core/modules/user/user.local_actions.yml
+++ b/core/modules/user/user.local_actions.yml
@@ -3,3 +3,8 @@ user_admin_create:
   title: 'Add user'
   appears_on:
     - user.admin_account
+user.role_add:
+  route_name: user.role_add
+  title: 'Add role'
+  appears_on:
+    - user.role_list
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 83baa54..8dfae6a 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -781,11 +781,6 @@ function user_menu() {
     'route_name' => 'user.admin_permissions',
     'type' => MENU_LOCAL_TASK,
   );
-  $items['admin/people/roles/add'] = array(
-    'title' => 'Add role',
-    'route_name' => 'user.role_add',
-    'type' => MENU_LOCAL_ACTION,
-  );
 
   $items['admin/people/roles/manage/%user_role'] = array(
     'title' => 'Edit role',
diff --git a/core/profiles/standard/config/entity.display.user.user.compact.yml b/core/profiles/standard/config/entity.display.user.user.compact.yml
new file mode 100644
index 0000000..d83a3dd
--- /dev/null
+++ b/core/profiles/standard/config/entity.display.user.user.compact.yml
@@ -0,0 +1,16 @@
+id: user.user.compact
+uuid: 70911b08-bf47-4a3f-b400-f99cf136effd
+targetEntityType: user
+bundle: user
+mode: compact
+content:
+  user_picture:
+    label: hidden
+    type: image
+    settings:
+      image_style: thumbnail
+      image_link: content
+    weight: 0
+  member_for:
+    visible: false
+status: true
diff --git a/core/profiles/standard/config/entity.display.user.user.default.yml b/core/profiles/standard/config/entity.display.user.user.default.yml
new file mode 100644
index 0000000..b19d602
--- /dev/null
+++ b/core/profiles/standard/config/entity.display.user.user.default.yml
@@ -0,0 +1,14 @@
+id: user.user.default
+uuid: 7a60b270-a584-46dc-ba00-a510230e9f45
+targetEntityType: user
+bundle: user
+mode: default
+content:
+  user_picture:
+    label: hidden
+    type: image
+    settings:
+      image_style: thumbnail
+      image_link: content
+    weight: 0
+status: true
diff --git a/core/profiles/standard/config/entity.form_display.user.user.default.yml b/core/profiles/standard/config/entity.form_display.user.user.default.yml
new file mode 100644
index 0000000..0758cf6
--- /dev/null
+++ b/core/profiles/standard/config/entity.form_display.user.user.default.yml
@@ -0,0 +1,13 @@
+id: user.user.default
+uuid: 9a01a9bf-791b-4e46-8d32-50136be8596f
+targetEntityType: user
+bundle: user
+mode: default
+content:
+  user_picture:
+    type: image_image
+    settings:
+      progress_indicator: throbber
+      preview_image_style: thumbnail
+    weight: -1
+status: true
diff --git a/core/profiles/standard/config/field.field.user.user_picture.yml b/core/profiles/standard/config/field.field.user.user_picture.yml
new file mode 100644
index 0000000..27c3069
--- /dev/null
+++ b/core/profiles/standard/config/field.field.user.user_picture.yml
@@ -0,0 +1,31 @@
+id: user.user_picture
+uuid: 745b0ce0-aece-42dd-a800-ade5b8455e84
+status: true
+langcode: en
+name: user_picture
+entity_type: user
+type: image
+settings:
+  uri_scheme: public
+  default_image: false
+  column_groups:
+    file:
+      label: File
+      columns:
+        - target_id
+        - width
+        - height
+    alt:
+      label: Alt
+      translatable: true
+    title:
+      label: Title
+      translatable: true
+module: image
+active: true
+locked: false
+cardinality: 1
+translatable: false
+indexes:
+  target_id:
+    - target_id
diff --git a/core/profiles/standard/config/field.instance.user.user.user_picture.yml b/core/profiles/standard/config/field.instance.user.user.user_picture.yml
new file mode 100644
index 0000000..2a3beec
--- /dev/null
+++ b/core/profiles/standard/config/field.instance.user.user.user_picture.yml
@@ -0,0 +1,29 @@
+id: user.user.user_picture
+uuid: 1e125e81-5211-4c73-a500-c45099ab9014
+status: true
+langcode: en
+field_uuid: 745b0ce0-aece-42dd-a800-ade5b8455e84
+entity_type: user
+bundle: user
+label: Picture
+description: 'Your virtual face or picture.'
+required: false
+default_value: {  }
+default_value_function: ''
+settings:
+  file_extensions: 'png gif jpg jpeg'
+  file_directory: pictures
+  max_filesize: '30 KB'
+  alt_field: 0
+  title_field: 0
+  max_resolution: 85x85
+  min_resolution: ''
+  default_image:
+    fid: null
+    alt: ''
+    title: ''
+    width: null
+    height: null
+  alt_field_required: '0'
+  title_field_required: '0'
+field_type: image
diff --git a/core/profiles/standard/standard.install b/core/profiles/standard/standard.install
index 5aa1dd7..e127a2f 100644
--- a/core/profiles/standard/standard.install
+++ b/core/profiles/standard/standard.install
@@ -31,10 +31,6 @@ function standard_install() {
   $user_settings = \Drupal::config('user.settings');
   $user_settings->set('register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)->save();
 
-  // Create user picture field.
-  module_load_install('user');
-  user_install_picture_field();
-
   // Enable default permissions for system roles.
   user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access content', 'access comments'));
   user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'skip comment approval'));
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index ae489e1..4144ef6 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -367,7 +367,8 @@ function simpletest_script_execute_batch($test_classes) {
       $test_class = array_shift($test_classes);
       // Process phpunit tests immediately since they are fast and we don't need
       // to fork for them.
-      if (is_subclass_of($test_class, 'Drupal\Tests\UnitTestCase')) {
+      if (is_subclass_of($test_class, 'Drupal\Tests\UnitTestCase') ||
+          is_subclass_of($test_class, '\PHPUnit_Framework_TestCase')) {
         simpletest_script_run_phpunit($test_id, $test_class);
         continue;
       }
@@ -668,7 +669,10 @@ function simpletest_script_get_test_list() {
         }
         else {
           foreach ($matches[1] as $class_name) {
-            $test_list[] = $namespace . '\\' . $class_name;
+            $namespace_class = $namespace . '\\' . $class_name;
+            if (is_subclass_of($namespace_class, '\Drupal\simpletest\TestBase') || is_subclass_of($namespace_class, '\Drupal\Tests\UnitTestCase')) {
+              $test_list[] = $namespace_class;
+            }
           }
         }
       }
diff --git a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
new file mode 100644
index 0000000..8c10669
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Access\CsrfAccessCheckTest.
+ */
+
+namespace Drupal\Tests\Core\Access;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+use Drupal\Core\Access\CsrfAccessCheck;
+use Drupal\Core\Access\AccessInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the CSRF access checker..
+ *
+ * @group Drupal
+ * @group Access
+ *
+ * @see \Drupal\Core\Access\CsrfAccessCheck
+ */
+class CsrfAccessCheckTest extends UnitTestCase {
+
+  /**
+   * The mock CSRF token generator.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $csrfToken;
+
+  /**
+   * The access checker.
+   *
+   * @var \Drupal\Core\Access\CsrfAccessCheck
+   */
+  protected $accessCheck;
+
+  /**
+   * The mock user account.
+   *
+   * @var \Drupal\Core\Session\AccountInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $account;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'CSRF access checker',
+      'description' => 'Tests CSRF access control for routes.',
+      'group' => 'Routing',
+    );
+  }
+
+  public function setUp() {
+    $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
+
+    $this->accessCheck = new CsrfAccessCheck($this->csrfToken);
+  }
+
+  /**
+   * Tests CsrfAccessCheck::appliesTo().
+   */
+  public function testAppliesTo() {
+    $this->assertEquals($this->accessCheck->appliesTo(), array('_csrf_token'), 'Access checker returned the expected appliesTo() array.');
+  }
+
+  /**
+   * Tests the access() method with a valid token.
+   */
+  public function testAccessTokenPass() {
+    $this->csrfToken->expects($this->once())
+      ->method('validate')
+      ->with('test_query', 'test')
+      ->will($this->returnValue(TRUE));
+
+    $route = new Route('', array(), array('_csrf_token' => 'test'));
+    $request = new Request(array(
+      'token' => 'test_query',
+    ));
+    // Set the _controller_request flag so tokens are validated.
+    $request->attributes->set('_controller_request', TRUE);
+
+    $this->assertSame(AccessInterface::ALLOW, $this->accessCheck->access($route, $request, $this->account));
+  }
+
+  /**
+   * Tests the access() method with an invalid token.
+   */
+  public function testAccessTokenFail() {
+    $this->csrfToken->expects($this->once())
+      ->method('validate')
+      ->with('test_query', 'test')
+      ->will($this->returnValue(FALSE));
+
+    $route = new Route('', array(), array('_csrf_token' => 'test'));
+    $request = new Request(array(
+      'token' => 'test_query',
+    ));
+    // Set the _controller_request flag so tokens are validated.
+    $request->attributes->set('_controller_request', TRUE);
+
+    $this->assertSame(AccessInterface::KILL, $this->accessCheck->access($route, $request, $this->account));
+  }
+
+  /**
+   * Tests the access() method with no _controller_request attribute set.
+   *
+   * This will default to the 'ANY' access conjuction.
+   */
+  public function testAccessTokenMissAny() {
+    $this->csrfToken->expects($this->never())
+      ->method('validate');
+
+    $route = new Route('', array(), array('_csrf_token' => 'test'));
+    $request = new Request(array(
+      'token' => 'test_query',
+    ));
+
+    $this->assertSame(AccessInterface::DENY, $this->accessCheck->access($route, $request, $this->account));
+  }
+
+  /**
+   * Tests the access() method with no _controller_request attribute set.
+   *
+   * This will use the 'ALL' access conjuction.
+   */
+  public function testAccessTokenMissAll() {
+    $this->csrfToken->expects($this->never())
+      ->method('validate');
+
+    $route = new Route('', array(), array('_csrf_token' => 'test'), array('_access_mode' => 'ALL'));
+    $request = new Request(array(
+      'token' => 'test_query',
+    ));
+
+    $this->assertSame(AccessInterface::ALLOW, $this->accessCheck->access($route, $request, $this->account));
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php b/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
new file mode 100644
index 0000000..37eec23
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Access\RouteProcessorCsrfTest.
+ */
+
+namespace Drupal\Tests\Core\Access;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\Access\RouteProcessorCsrf;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests the CSRF route processor.
+ *
+ * @see Drupal
+ * @see Routing
+ *
+ * @see \Drupal\Core\Access\RouteProcessorCsrf
+ */
+class RouteProcessorCsrfTest extends UnitTestCase {
+
+  /**
+   * The mock CSRF token generator.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $csrfToken;
+
+  /**
+   * The route processor.
+   *
+   * @var \Drupal\Core\Access\RouteProcessorCsrf
+   */
+  protected $processor;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'CSRF access checker',
+      'description' => 'Tests CSRF access control for routes.',
+      'group' => 'Routing',
+    );
+  }
+
+  public function setUp() {
+    $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->processor = new RouteProcessorCsrf($this->csrfToken);
+  }
+
+  /**
+ * Tests the processOutbound() method with no _csrf_token route requirement.
+ */
+  public function testProcessOutboundNoRequirement() {
+    $this->csrfToken->expects($this->never())
+      ->method('get');
+
+    $route = new Route('');
+    $parameters = array();
+
+    $this->processor->processOutbound($route, $parameters);
+    // No parameters should be added to the parameters array.
+    $this->assertEmpty($parameters);
+  }
+
+  /**
+   * Tests the processOutbound() method with a _csrf_token route requirement.
+   */
+  public function testProcessOutbound() {
+    $this->csrfToken->expects($this->once())
+      ->method('get')
+      ->with('test')
+      ->will($this->returnValue('test_token'));
+
+    $route = new Route('', array(), array('_csrf_token' => 'test'));
+    $parameters = array();
+
+    $this->processor->processOutbound($route, $parameters);
+    // 'token' should be added to the parameters array.
+    $this->assertArrayHasKey('token', $parameters);
+    $this->assertSame($parameters['token'], 'test_token');
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Installer/InstallerTest.php b/core/tests/Drupal/Tests/Core/Installer/InstallerTest.php
new file mode 100644
index 0000000..25262fd
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Installer/InstallerTest.php
@@ -0,0 +1,214 @@
+<?php
+
+namespace Drupal\Tests\Core\Installer;
+
+use Drupal\Core\Installer\Installer;
+use Drupal\Core\Database\Database;
+use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\DrupalKernel;
+
+/**
+ * @group slow
+ *
+ * This test must be isolated because the Installer will install Drupal
+ * in the same process this test is running in, which would otherwise pollute
+ * the environment of all other tests.
+ *
+ * @preserveGlobalState disabled
+ * @runTestsInSeparateProcesses
+ */
+class InstallerTest extends \PHPUnit_Framework_TestCase {
+
+  protected $installPrefix;
+  protected $defaultDbInfo;
+
+  static function getInfo() {
+    return array(
+      'name' => 'Installer acceptance test',
+      'description' => 'Acceptance test for the non-interactive Installer.',
+      'group' => 'Installer',
+    );
+  }
+
+  function setUp() {
+    // Require the autoload bootstrap file manually, since it won't be included
+    // when using "@preserveGlobalState disabled".
+    require $this->drupalRoot() . 'core/tests/autoload.php';
+    // Hack to prevent Drupal's shutdown functions from running, which would
+    // prevent this test from cleaning up the database it creates.
+    register_shutdown_function(function() { exit();});
+  }
+
+  function testInstall() {
+    // Setup a fake site to install.
+    $site_path = $this->siteDirectory();
+    mkdir($site_path);
+    file_put_contents($site_path . '/settings.php', '<?php' . PHP_EOL);
+    $install_hostname = $this->installHostName();
+
+    $installer = new Installer();
+    $options = $this->installSettings();
+    $installer
+      ->setProfile($options['profile'])
+      ->setLocale($options['locale'])
+      ->setAccountPassword($options['account_password'])
+      ->setAccountUsername($options['account_username'])
+      ->setAccountMail($options['account_mail'])
+      ->setCleanUrls($options['clean_urls'])
+      ->setSiteMail($options['site_mail'])
+      ->setSiteName($options['site_name'])
+      ->setDefaultDbSettings($options['db_driver'], $options['db_settings'])
+      ->setSite($install_hostname);
+    $installer->install();
+
+    // Simulate a request to make sure it comes back ok.
+    drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
+    $kernel = new \Drupal\Core\DrupalKernel('prod', drupal_classloader());
+    $kernel->boot();
+    drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
+    $request = new Request(array(), array(), array(), array(), array(),array(
+      'SCRIPT_URL' => '/',
+      'SCRIPT_URI' => 'http://' . $install_hostname,
+      'SCRIPT_NAME' => '/index.php',
+      'HTTP_HOST' => $install_hostname,
+      'SERVER_NAME' => $install_hostname,
+      'REQUEST_METHOD' => 'GET',
+      'REQUEST_URI' => '/',
+    ));
+    $response = $kernel->handle($request)->prepare($request);
+
+    $this->assertEquals(200, $response->getStatusCode(), 'The site returned a 200 HTTP response.');
+    $this->assertNotEquals(FALSE, strpos($response->getContent(), 'test-sitename'), 'The response was returned by the test site.');
+  }
+
+  function teardown() {
+    $this->recursiveRemoveDirectory($this->siteDirectory());
+
+    //$db_info = $this->installDbInfo();
+    $runner_site_db_info = $this->defaultDbInfo();
+
+    $connection_info = Database::getConnectionInfo('default');
+
+    $runner_db = $runner_site_db_info['prefix'] . $runner_site_db_info['database'];
+    $test_db = $connection_info['default']['prefix']['default'] .  $connection_info['default']['database'];
+
+    //
+    if (empty($runner_db) || $runner_db == $test_db) {
+      return;
+    }
+
+    $schema = Database::getConnection()->schema();
+
+    $tables = $schema->findTables($connection_info['default']['prefix']['default'] . '%');
+    $prefix_length = strlen($connection_info['default']['prefix']['default']);
+
+    foreach ($tables as $table) {
+      $unprefixed_table = substr($table, $prefix_length);
+      if ($schema->dropTable($unprefixed_table)) {
+        unset($tables[$table]);
+      }
+    }
+    if (!empty($tables)) {
+      $this->fail('Failed to drop all prefixed tables.');
+    }
+  }
+
+  /**
+   * Settings to be passed the installer object.
+   */
+  protected function installSettings() {
+    $db_info = $this->installDbInfo();
+    return array(
+      'profile' => 'standard',
+      'locale' => 'en',
+      'account_password' => 'testpassword',
+      'account_username' => 'testusername',
+      'account_mail' => 'account@example.com',
+      'clean_urls' => TRUE,
+      'site_mail' => 'sitemail@example.com',
+      'site_name' => 'test-sitename',
+      'db_driver' => 'mysql',
+      'db_settings' => $db_info,
+    );
+  }
+
+  /**
+   * Helper method to find what would be DRUPAL_ROOT.
+   */
+  protected function drupalRoot() {
+    return substr(__FILE__, 0, strpos(__FILE__, "core/tests/Drupal/Tests"));
+  }
+
+  /**
+   * Returns the database information for the default site, so we can use those
+   * creds to create a new database.
+   *
+   * TODO: Come up with a better way to give db creds to PHPUnit.
+   */
+  protected function defaultDbInfo() {
+    if ($this->defaultDbInfo) {
+      return $this->defaultDbInfo;
+    }
+
+    include_once $this->drupalRoot() . 'sites/default/settings.php';
+    return $this->defaultDbInfo = $databases['default']['default'];
+  }
+
+  /**
+   * Returns the database connection info to use for the test site.
+   */
+  protected function installDbInfo() {
+    $db_info = $this->defaultDbInfo();
+    if (!$db_info) {
+      $this->fail("Failed to find the site's database credentials.");
+    }
+    $db_info['db_prefix'] = $this->installPrefix();
+    return $db_info;
+  }
+
+  /**
+   * Generates a prefix to be used for the sitename and db name.
+   */
+  protected function installPrefix() {
+    if ($this->installPrefix) {
+      return $this->installPrefix;
+    }
+
+    return $this->installPrefix = 'installer_test_' . uniqid();
+  }
+
+  /**
+   * Returns the hostname to be used for the test site, using
+   * Drupal's multisite feture.
+   */
+  protected function installHostName() {
+    return $this->installPrefix() . '.example.com';
+  }
+
+  /**
+   * Returns the path to the site directory of the test site.
+   */
+  protected function siteDirectory() {
+    return $this->drupalRoot() . '/sites/' . $this->installHostName();
+  }
+
+  protected function recursiveRemoveDirectory($dir) {
+    if (!file_exists($dir)) {
+      return TRUE;
+    }
+    @chmod($dir, 0777); // Make file/dir writeable
+    if (!is_dir($dir)) {
+      return unlink($dir);
+    }
+    foreach (scandir($dir) as $item) {
+      if ($item == '.' || $item == '..') {
+        continue;
+      }
+      if (!$this->recursiveRemoveDirectory($dir.'/'.$item)) {
+        return FALSE;
+      }
+    }
+    return rmdir($dir);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Installer/InstallerUnitTest.php b/core/tests/Drupal/Tests/Core/Installer/InstallerUnitTest.php
new file mode 100644
index 0000000..be8a835
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Installer/InstallerUnitTest.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\Tests\Core\Installer;
+
+use Drupal\Core\Installer\Installer;
+
+/**
+ * This test needs to be isolated because the Installer class will define the
+ * MAINTENANCE_MODE constant, which should not leak into other tests.
+ *
+ * @preserveGlobalState disabled
+ * @runTestsInSeparateProcesses
+ */
+class InstallerUnitTest extends \PHPUnit_Framework_TestCase {
+
+  static function getInfo() {
+    return array(
+      'name' => 'Installer unit test',
+      'description' => 'Unit tests for the non-interactive Installer class',
+      'group' => 'Installer',
+    );
+  }
+
+  function setUp() {
+    $root = substr(__FILE__, 0, strpos(__FILE__, "core/tests/Drupal/Tests"));
+    // Require the autoload bootstrap file manually, since it won't be included
+    // when using "@preserveGlobalState disabled".
+    require $root . '/core/tests/autoload.php';
+  }
+
+  /**
+   * @expectedException Drupal\Core\Installer\InstallerException
+   */
+  function testMissingParametersValidation() {
+    $installer = new Installer();
+    $installer->setInstallCallback(function($settings) {});
+    $installer->setProfile('standard');
+    $installer->install();
+  }
+
+  function testInstall() {
+    $callback = $this->getMock('stdClass', array('install'));
+    $callback->expects($this->once())
+             ->method('install');
+
+    $installer = new Installer();
+    $installer->setInstallCallback(array($callback, 'install'));
+
+    $options = $this->installSettings();
+    $installer
+      ->setProfile($options['profile'])
+      ->setLocale($options['locale'])
+      ->setAccountPassword($options['account_password'])
+      ->setAccountUsername($options['account_username'])
+      ->setAccountMail($options['account_mail'])
+      ->setCleanUrls($options['clean_urls'])
+      ->setSiteMail($options['site_mail'])
+      ->setSiteName($options['site_name'])
+      ->setDefaultDbSettings($options['db_driver'], $options['db_settings']);
+
+    $installer->install();
+  }
+
+  /**
+   * Settings to be used when preparing the Installer class.
+   */
+  protected function installSettings() {
+    return array(
+      'profile' => 'standard',
+      'locale' => 'en',
+      'account_password' => 'testpassword',
+      'account_username' => 'testusername',
+      'account_mail' => 'account@example.com',
+      'clean_urls' => TRUE,
+      'site_mail' => 'sitemail@example.com',
+      'site_name' => 'test sitename',
+      'db_driver' => 'mysql',
+      'db_settings' => array(
+         'driver' => 'mysql',
+         'name' => 'd8',
+         'host' => 'localhost',
+         'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
+         'username' => 'root',
+         'password' => '',
+         'port' => 3306,
+      )
+    );
+  }
+}
diff --git a/core/tests/Drupal/Tests/Core/RouteProcessor/RouteProcessorManagerTest.php b/core/tests/Drupal/Tests/Core/RouteProcessor/RouteProcessorManagerTest.php
new file mode 100644
index 0000000..6d23ca6
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/RouteProcessor/RouteProcessorManagerTest.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\RouteProcessor\RouteProcessorManagerTest.
+ */
+
+namespace Drupal\Tests\Core\RouteProcessor;
+
+use Drupal\Core\RouteProcessor\RouteProcessorManager;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests the RouteProcessorManager class.
+ *
+ * @group Drupal
+ * @group Routing
+ *
+ * @see \Drupal\Core\RouteProcessor\RouteProcessorManager
+ */
+class RouteProcessorManagerTest extends UnitTestCase {
+
+  /**
+   * The route processor manager.
+   *
+   * @var \Drupal\Core\RouteProcessor\RouteProcessorManager
+   */
+  protected $processorManager;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Route processor manager',
+      'description' => 'Tests the RouteProcessorManager class.',
+      'group' => 'Routing',
+    );
+  }
+
+  public function setUp() {
+    $this->processorManager = new RouteProcessorManager();
+  }
+
+  /**
+   * Tests the Route process manager functionality.
+   */
+  public function testRouteProcessorManager() {
+    $route = new Route('');
+    $parameters = array('test' => 'test');
+
+    $processors = array(
+      10 => $this->getMockProcessor($route, $parameters),
+      5 => $this->getMockProcessor($route, $parameters),
+      0 => $this->getMockProcessor($route, $parameters),
+    );
+
+    // Add the processors in reverse order.
+    foreach ($processors as $priority => $processor) {
+      $this->processorManager->addOutbound($processor, $priority);
+    }
+
+    $this->processorManager->processOutbound($route, $parameters);
+  }
+
+  /**
+   * Returns a mock Route processor object.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The Route to use in mock with() expectation.
+   * @param array $parameters
+   *   The parameters to use in mock with() expectation.
+   *
+   * @return \Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected function getMockProcessor($route, $parameters) {
+    $processor = $this->getMock('Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface');
+    $processor->expects($this->once())
+      ->method('processOutbound')
+      ->with($route, $parameters);
+
+    return $processor;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
index 75d051d..d1c59c9 100644
--- a/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
@@ -8,21 +8,15 @@
 namespace Drupal\Tests\Core\Routing;
 
 use Drupal\Component\Utility\Settings;
-use Drupal\Core\Config\ConfigFactory;
-use Drupal\Core\Config\NullStorage;
-use Drupal\Core\Config\Context\ConfigContextFactory;
 use Drupal\Core\PathProcessor\PathProcessorAlias;
 use Drupal\Core\PathProcessor\PathProcessorManager;
-use Symfony\Component\EventDispatcher\EventDispatcher;
+use Drupal\Core\Routing\UrlGenerator;
+use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 use Symfony\Component\Routing\RequestContext;
 
-use Drupal\Tests\UnitTestCase;
-
-use Drupal\Core\Routing\UrlGenerator;
-
 /**
  * Basic tests for the Route.
  *
@@ -44,8 +38,20 @@ class UrlGeneratorTest extends UnitTestCase {
    */
   protected $generatorMixedMode;
 
+  /**
+   * The alias manager.
+   *
+   * @var \Drupal\Core\Path\AliasManager|\PHPUnit_Framework_MockObject_MockObject
+   */
   protected $aliasManager;
 
+  /**
+   * The mock route processor manager.
+   *
+   * @var \Drupal\Core\RouteProcessor\RouteProcessorManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $routeProcessorManager;
+
   public static function getInfo() {
     return array(
       'name' => 'UrlGenerator',
@@ -121,14 +127,18 @@ function setUp() {
     $processor_manager = new PathProcessorManager();
     $processor_manager->addOutbound($processor, 1000);
 
+    $this->routeProcessorManager = $this->getMockBuilder('Drupal\Core\RouteProcessor\RouteProcessorManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
     $config_factory_stub = $this->getConfigFactoryStub(array('system.filter' => array('protocols' => array('http', 'https'))));
 
-    $generator = new UrlGenerator($provider, $processor_manager, $config_factory_stub, new Settings(array()));
+    $generator = new UrlGenerator($provider, $processor_manager, $this->routeProcessorManager, $config_factory_stub, new Settings(array()));
     $generator->setContext($context);
     $this->generator = $generator;
 
     // Second generator for mixed-mode sessions.
-    $generator = new UrlGenerator($provider, $processor_manager, $config_factory_stub, new Settings(array('mixed_mode_sessions' => TRUE)));
+    $generator = new UrlGenerator($provider, $processor_manager, $this->routeProcessorManager, $config_factory_stub, new Settings(array('mixed_mode_sessions' => TRUE)));
     $generator->setContext($context);
     $this->generatorMixedMode = $generator;
   }
@@ -163,6 +173,11 @@ public function testAliasGeneration() {
     $url = $this->generator->generate('test_1');
     $this->assertEquals('/hello/world', $url);
 
+    $this->routeProcessorManager->expects($this->once())
+      ->method('processOutbound')
+      ->with($this->anything());
+
+
     // Check that the two generate methods return the same result.
     $url_from_route = $this->generator->generateFromRoute('test_1');
     $this->assertEquals($url_from_route, $url);
@@ -177,6 +192,9 @@ public function testAliasGeneration() {
   public function testGetPathFromRouteWithSubdirectory() {
     $this->generator->setBasePath('/test-base-path');
 
+    $this->routeProcessorManager->expects($this->never())
+      ->method('processOutbound');
+
     $path = $this->generator->getPathFromRoute('test_1');
     $this->assertEquals('test/one', $path);
   }
@@ -188,6 +206,10 @@ public function testAliasGenerationWithParameters() {
     $url = $this->generator->generate('test_2', array('narf' => '5'));
     $this->assertEquals('/goodbye/cruel/world', $url);
 
+    $this->routeProcessorManager->expects($this->exactly(3))
+      ->method('processOutbound')
+      ->with($this->anything());
+
     $options = array('fragment' => 'top');
     // Extra parameters should appear in the query string.
     $url = $this->generator->generateFromRoute('test_1', array('zoo' => '5'), $options);
@@ -209,6 +231,9 @@ public function testAliasGenerationWithParameters() {
    * Tests URL generation from route with trailing start and end slashes.
    */
   public function testGetPathFromRouteTrailing() {
+    $this->routeProcessorManager->expects($this->never())
+      ->method('processOutbound');
+
     $path = $this->generator->getPathFromRoute('test_3');
     $this->assertEquals($path, 'test/two');
   }
@@ -220,6 +245,10 @@ public function testAbsoluteURLGeneration() {
     $url = $this->generator->generate('test_1', array(), TRUE);
     $this->assertEquals('http://localhost/hello/world', $url);
 
+    $this->routeProcessorManager->expects($this->once())
+      ->method('processOutbound')
+      ->with($this->anything());
+
     $options = array('absolute' => TRUE, 'fragment' => 'top');
     // Extra parameters should appear in the query string.
     $url = $this->generator->generateFromRoute('test_1', array('zoo' => '5'), $options);
@@ -233,6 +262,10 @@ public function testUrlGenerationWithHttpsRequirement() {
     $url = $this->generator->generate('test_4', array(), TRUE);
     $this->assertEquals('https://localhost/test/four', $url);
 
+    $this->routeProcessorManager->expects($this->exactly(2))
+      ->method('processOutbound')
+      ->with($this->anything());
+
     $options = array('absolute' => TRUE, 'https' => TRUE);
     // Mixed-mode sessions are not enabled, so the https option is ignored.
     $url = $this->generator->generateFromRoute('test_1', array(), $options);
diff --git a/core/tests/bootstrap.php b/core/tests/autoload.php
similarity index 86%
copy from core/tests/bootstrap.php
copy to core/tests/autoload.php
index c69a52f..fe0a848 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/autoload.php
@@ -80,15 +80,3 @@ function drupal_phpunit_register_extension_dirs(Composer\Autoload\ClassLoader $l
 $dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots);
 $dirs = array_reduce($dirs, 'array_merge', array());
 drupal_phpunit_register_extension_dirs($loader, $dirs);
-
-// Look into removing this later.
-define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
-
-// Set sane locale settings, to ensure consistent string, dates, times and
-// numbers handling.
-// @see drupal_environment_initialize()
-setlocale(LC_ALL, 'C');
-
-// Set the default timezone. While this doesn't cause any tests to fail, PHP
-// complains if 'date.timezone' is not set in php.ini.
-date_default_timezone_set('UTC');
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
index c69a52f..937a8d5 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/bootstrap.php
@@ -7,79 +7,7 @@
  * @see phpunit.xml.dist
  */
 
-/**
- * Finds all valid extension directories recursively within a given directory.
- *
- * @param string $scan_directory
- *   The directory that should be recursively scanned.
- * @return array
- *   An associative array of extension directories found within the scanned
- *   directory, keyed by extension name.
- */
-function drupal_phpunit_find_extension_directories($scan_directory) {
-  $extensions = array();
-  $dirs = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($scan_directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS));
-  foreach ($dirs as $dir) {
-    if (strpos($dir->getPathname(), 'info.yml') !== FALSE) {
-      // Cut off ".info.yml" from the filename for use as the extension name.
-      $extensions[substr($dir->getFilename(), 0, -9)] = $dir->getPathInfo()->getRealPath();
-    }
-  }
-  return $extensions;
-}
-
-/**
- * Returns directories under which contributed extensions may exist.
- *
- * @return array
- *   An array of directories under which contributed extensions may exist.
- */
-function drupal_phpunit_contrib_extension_directory_roots() {
-  $sites_path = __DIR__ . '/../../sites';
-  $paths = array();
-  // Note this also checks sites/../modules and sites/../profiles.
-  foreach (scandir($sites_path) as $site) {
-    $path = "$sites_path/$site";
-    $paths[] = is_dir("$path/modules") ? realpath("$path/modules") : NULL;
-    $paths[] = is_dir("$path/profiles") ? realpath("$path/profiles") : NULL;
-  }
-  return array_filter($paths);
-}
-
-/**
- * Registers the namespace for each extension directory with the autoloader.
- *
- * @param Composer\Autoload\ClassLoader $loader
- *   The supplied autoloader.
- * @param array $dirs
- *   An associative array of extension directories, keyed by extension name.
- */
-function drupal_phpunit_register_extension_dirs(Composer\Autoload\ClassLoader $loader, $dirs) {
-  foreach ($dirs as $extension => $dir) {
-    $lib_path = $dir . '/lib';
-    if (is_dir($lib_path)) {
-      $loader->add('Drupal\\' . $extension, $lib_path);
-    }
-    $tests_path = $dir . '/tests';
-    if (is_dir($tests_path)) {
-      $loader->add('Drupal\\' . $extension, $tests_path);
-    }
-  }
-}
-
-// Start with classes in known locations.
-$loader = require __DIR__ . '/../vendor/autoload.php';
-$loader->add('Drupal\\Tests', __DIR__);
-
-// Scan for arbitrary extension namespaces from core and contrib.
-$extension_roots = array_merge(array(
-  __DIR__ . '/../modules',
-  __DIR__ . '/../profiles',
-), drupal_phpunit_contrib_extension_directory_roots());
-
-$dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots);
-$dirs = array_reduce($dirs, 'array_merge', array());
-drupal_phpunit_register_extension_dirs($loader, $dirs);
+require "autoload.php";
 
 // Look into removing this later.
 define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
diff --git a/core/themes/seven/install-page.css b/core/themes/seven/install-page.css
index 0d223f0..42d3ea2 100644
--- a/core/themes/seven/install-page.css
+++ b/core/themes/seven/install-page.css
@@ -27,28 +27,24 @@ body.install-page {
   background: none;
 }
 @media all and (min-width: 48em) { /* 768px */
+  html {
+    display: table;
+  }
+  body {
+    display: table-cell;
+    padding: 1em 0;
+    vertical-align: middle;
+  }
   html, body {
-    height: 100%;
-    text-align: center;
-    white-space: nowrap;
     margin: 0;
-  }
-  body:before {
-    content: '';
-    display: inline-block;
+    width: 100%;
     height: 100%;
-    width: 0;
-    vertical-align: middle;
-    margin-right: -0.35em;
   }
   body.install-page #page {
-    display: inline-block;
-    vertical-align: middle;
-    text-align: left;
-    white-space: normal;
-    border-radius: 5px;
-    width: 75%;
+    margin: 0 auto;
     max-width: 770px;
+    width: 75%;
+    border-radius: 5px;
     box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
   }
   body.install-page #content {
diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme
index 67ebb0f..469abc8 100644
--- a/core/themes/seven/seven.theme
+++ b/core/themes/seven/seven.theme
@@ -191,7 +191,6 @@ function seven_form_node_form_alter(&$form, &$form_state) {
 
   $form['advanced']['#type'] = 'container';
   $form['meta'] = array (
-    '#type' => 'fieldset',
     '#attributes' => array('class' => array('entity-meta-header')),
     '#type' => 'container',
     '#group' => 'advanced',
diff --git a/core/themes/stark/stark.info.yml b/core/themes/stark/stark.info.yml
index d030de8..6a98889 100644
--- a/core/themes/stark/stark.info.yml
+++ b/core/themes/stark/stark.info.yml
@@ -7,3 +7,5 @@ core: 8.x
 stylesheets:
   all:
     - css/layout.css
+stylesheets-remove:
+  - normalize.css
diff --git a/core/update.php b/core/update.php
index a77b5cd..7abd566 100644
--- a/core/update.php
+++ b/core/update.php
@@ -159,6 +159,7 @@ function update_script_selection_form($form, &$form_state) {
     $form['actions']['submit'] = array(
       '#type' => 'submit',
       '#value' => 'Apply pending updates',
+      '#button_type' => 'primary',
     );
   }
   return $form;
@@ -310,7 +311,7 @@ function update_info_page() {
   $output .= "</ol>\n";
   $output .= "<p>When you have performed the steps above, you may proceed.</p>\n";
   $form_action = check_url(drupal_current_script_url(array('op' => 'selection', 'token' => $token)));
-  $output .= '<form method="post" action="' . $form_action . '"><p><input type="submit" value="Continue" class="form-submit" /></p></form>';
+  $output .= '<form method="post" action="' . $form_action . '"><p><input type="submit" value="Continue" class="form-submit button button-primary" /></p></form>';
   $output .= "\n";
   return $output;
 }
