diff --git a/core/authorize.php b/core/authorize.php
index d9f1e56a8d851c33d3fbf5f9c3376345d9f58a71..1138c92b89ee63e7f8e49961556425515b82202b 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -139,7 +139,13 @@ function authorize_access_allowed(Request $request) {
   }
   // If a batch is running, let it run.
   elseif ($request->query->has('batch')) {
-    $content = ['#markup' => _batch_page($request)];
+    $content = _batch_page($request);
+    // If _batch_page() returns a response object (likely a JsonResponse for
+    // JavaScript-based batch processing), send it immediately.
+    if ($content instanceof Response) {
+      $content->send();
+      exit;
+    }
   }
   else {
     if (empty($_SESSION['authorize_operation']) || empty($_SESSION['authorize_filetransfer_info'])) {
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 16779afd1962a1b6f90b904016e0ac4fddf77992..d23addb73fc11adfa37e90d75430cee3427713dc 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -124,7 +124,10 @@ function theme_authorize_report($variables) {
           '#message' => $log_message['message'],
           '#success' => $log_message['success'],
         );
-        $items[] = drupal_render($authorize_message);
+        $items[] = array(
+          '#markup' => drupal_render($authorize_message),
+          '#wrapper_attributes' => array('class' => $log_message['success'] ? 'authorize-results__success' : 'authorize-results__failure'),
+        );
       }
       $item_list = array(
         '#theme' => 'item_list',
@@ -151,13 +154,6 @@ function theme_authorize_report($variables) {
  * @ingroup themeable
  */
 function theme_authorize_message($variables) {
-  $message = $variables['message'];
-  $success = $variables['success'];
-  if ($success) {
-    $item = array('data' => array('#markup' => $message), 'class' => array('authorize-results__success'));
-  }
-  else {
-    $item = array('data' => array('#markup' => $message), 'class' => array('authorize-results__failure'));
-  }
-  return $item;
+  $item = array('#markup' => $variables['message']);
+  return drupal_render($item);
 }
diff --git a/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php b/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
index 226e93e20ca33b759154ba36a4e6e6ba586897ee..7bc0f1cc518f8e1d0d98389a02bc3c297329c64c 100644
--- a/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
+++ b/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Provides the file transfer authorization form.
@@ -226,7 +227,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
           $filetransfer = $this->getFiletransfer($filetransfer_backend, $form_connection_settings[$filetransfer_backend]);
 
           // Now run the operation.
-          $this->runOperation($filetransfer);
+          $response = $this->runOperation($filetransfer);
+          if ($response instanceof Response) {
+            $form_state->setResponse($response);
+          }
         }
         catch (\Exception $e) {
           // If there is no database available, we don't care and just skip
@@ -333,13 +337,18 @@ protected function setConnectionSettingsDefaults(&$element, $key, array $default
    *
    * @param $filetransfer
    *   The FileTransfer object to use for running the operation.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response|null
+   *   The result of running the operation. If this is an instance of
+   *   \Symfony\Component\HttpFoundation\Response the calling code should use
+   *   that response for the current page request.
    */
   protected function runOperation($filetransfer) {
     $operation = $_SESSION['authorize_operation'];
     unset($_SESSION['authorize_operation']);
 
     require_once $this->root . '/' . $operation['file'];
-    call_user_func_array($operation['callback'], array_merge(array($filetransfer), $operation['arguments']));
+    return call_user_func_array($operation['callback'], array_merge(array($filetransfer), $operation['arguments']));
   }
 
 }
diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php
index 25c03f96124db305366996b024659bf894f24be0..c495272796ab07b05e395137113957875f257af5 100644
--- a/core/lib/Drupal/Core/Updater/Module.php
+++ b/core/lib/Drupal/Core/Updater/Module.php
@@ -20,7 +20,7 @@ class Module extends Updater implements UpdaterInterface {
    *
    * If the module is already installed, drupal_get_path() will return
    * a valid path and we should install it there (although we need to use an
-   * absolute path, so we prepend DRUPAL_ROOT). If we're installing a new
+   * absolute path, so we prepend the root path). If we're installing a new
    * module, we always want it to go into /modules, since that's
    * where all the documentation recommends users install their modules, and
    * there's no way that can conflict on a multi-site installation, since
@@ -31,20 +31,30 @@ class Module extends Updater implements UpdaterInterface {
    *   A directory path.
    */
   public function getInstallDirectory() {
-    if ($relative_path = drupal_get_path('module', $this->name)) {
+    if ($this->isInstalled() && ($relative_path = drupal_get_path('module', $this->name))) {
       $relative_path = dirname($relative_path);
     }
     else {
-      $relative_path = 'modules';
+      $relative_path = $this->getRootDirectoryRelativePath();
     }
-    return DRUPAL_ROOT . '/' . $relative_path;
+    return $this->root . '/' . $relative_path;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getRootDirectoryRelativePath() {
+    return 'modules';
   }
 
   /**
    * Implements Drupal\Core\Updater\UpdaterInterface::isInstalled().
    */
   public function isInstalled() {
-    return (bool) drupal_get_path('module', $this->name);
+    // Check if the module exists in the file system, regardless of whether it
+    // is enabled or not.
+    $modules = \Drupal::state()->get('system.module.files', array());
+    return isset($modules[$this->name]);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Updater/Theme.php b/core/lib/Drupal/Core/Updater/Theme.php
index 8ad55f0d520d9255f02e2366c828f8722827fbf2..519f110b52834fa0feabedc7229d3f5a13d0d7df 100644
--- a/core/lib/Drupal/Core/Updater/Theme.php
+++ b/core/lib/Drupal/Core/Updater/Theme.php
@@ -20,7 +20,7 @@ class Theme extends Updater implements UpdaterInterface {
    *
    * If the theme is already installed, drupal_get_path() will return
    * a valid path and we should install it there (although we need to use an
-   * absolute path, so we prepend DRUPAL_ROOT). If we're installing a new
+   * absolute path, so we prepend the root path). If we're installing a new
    * theme, we always want it to go into /themes, since that's
    * where all the documentation recommends users install their themes, and
    * there's no way that can conflict on a multi-site installation, since
@@ -31,20 +31,30 @@ class Theme extends Updater implements UpdaterInterface {
    *   A directory path.
    */
   public function getInstallDirectory() {
-    if ($relative_path = drupal_get_path('theme', $this->name)) {
+    if ($this->isInstalled() && ($relative_path = drupal_get_path('theme', $this->name))) {
       $relative_path = dirname($relative_path);
     }
     else {
-      $relative_path = 'themes';
+      $relative_path = $this->getRootDirectoryRelativePath();
     }
-    return DRUPAL_ROOT . '/' . $relative_path;
+    return $this->root . '/' . $relative_path;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getRootDirectoryRelativePath() {
+    return 'themes';
   }
 
   /**
    * Implements Drupal\Core\Updater\UpdaterInterface::isInstalled().
    */
   public function isInstalled() {
-    return (bool) drupal_get_path('theme', $this->name);
+    // Check if the theme exists in the file system, regardless of whether it
+    // is enabled or not.
+    $themes = \Drupal::state()->get('system.theme.files', array());
+    return isset($themes[$this->name]);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Updater/Updater.php b/core/lib/Drupal/Core/Updater/Updater.php
index 4f56cd9e8377f62a5afa8ac8e520e34734f48819..4869b1cfec1b75cf2fe89f8e6cb8688b850f5efd 100644
--- a/core/lib/Drupal/Core/Updater/Updater.php
+++ b/core/lib/Drupal/Core/Updater/Updater.php
@@ -23,14 +23,26 @@ class Updater {
    */
   public $source;
 
+  /**
+   * The root directory under which new projects will be copied.
+   *
+   * @var string
+   */
+  protected $root;
+
   /**
    * Constructs a new updater.
    *
    * @param string $source
    *   Directory to install from.
+   * @param string $root
+   *   The root directory under which the project will be copied to if it's a
+   *   new project. Usually this is the app root (the directory in which the
+   *   Drupal site is installed).
    */
-  public function __construct($source) {
+  public function __construct($source, $root) {
     $this->source = $source;
+    $this->root = $root;
     $this->name = self::getProjectName($source);
     $this->title = self::getProjectTitle($source);
   }
@@ -43,20 +55,24 @@ public function __construct($source) {
    *
    * @param string $source
    *   Directory of a Drupal project.
+   * @param string $root
+   *   The root directory under which the project will be copied to if it's a
+   *   new project. Usually this is the app root (the directory in which the
+   *   Drupal site is installed).
    *
    * @return \Drupal\Core\Updater\Updater
    *   A new Drupal\Core\Updater\Updater object.
    *
    * @throws \Drupal\Core\Updater\UpdaterException
    */
-  public static function factory($source) {
+  public static function factory($source, $root) {
     if (is_dir($source)) {
       $updater = self::getUpdaterFromDirectory($source);
     }
     else {
       throw new UpdaterException(t('Unable to determine the type of the source directory.'));
     }
-    return new $updater($source);
+    return new $updater($source, $root);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Updater/UpdaterInterface.php b/core/lib/Drupal/Core/Updater/UpdaterInterface.php
index 7cd7512c6d12220486b7db2fc18915ed8b4f0f50..331e84068ad36b3ed7e8785d6d83d8f8ab08dda3 100644
--- a/core/lib/Drupal/Core/Updater/UpdaterInterface.php
+++ b/core/lib/Drupal/Core/Updater/UpdaterInterface.php
@@ -35,13 +35,21 @@ public function isInstalled();
   public static function getProjectName($directory);
 
   /**
-   * Returns the path to the default install location.
+   * Returns the path to the default install location for the current project.
    *
    * @return string
    *   An absolute path to the default install location.
    */
   public function getInstallDirectory();
 
+  /**
+   * Returns the name of the root directory under which projects will be copied.
+   *
+   * @return string
+   *   A relative path to the root directory.
+   */
+  public static function getRootDirectoryRelativePath();
+
   /**
    * Determines if the Updater can handle the project provided in $directory.
    *
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 027ba8bd20476be96f0a59bf7efd11c5352389b1..d6d98c8f812bb449c69b7942ea775d9109d24f30 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -460,7 +460,7 @@ function system_authorized_run($callback, $file, $arguments = array(), $page_tit
 function system_authorized_batch_process() {
   $finish_url = system_authorized_get_url();
   $process_url = system_authorized_batch_processing_url();
-  return batch_process($finish_url->toString(), $process_url);
+  return batch_process($finish_url->setAbsolute()->toString(), $process_url);
 }
 
 /**
diff --git a/core/modules/update/src/Form/UpdateManagerInstall.php b/core/modules/update/src/Form/UpdateManagerInstall.php
index b37f2464d06e72e2bb201b2a533f82d8a9aa0dc1..bb67ec6c455015c0b3d669072bd9d72838acd749 100644
--- a/core/modules/update/src/Form/UpdateManagerInstall.php
+++ b/core/modules/update/src/Form/UpdateManagerInstall.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Updater\Updater;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Configure update settings for this site.
@@ -27,7 +28,7 @@ class UpdateManagerInstall extends FormBase {
   protected $moduleHandler;
 
   /**
-   * The app root.
+   * The root location under which installed projects will be saved.
    *
    * @var string
    */
@@ -44,7 +45,7 @@ class UpdateManagerInstall extends FormBase {
    * Constructs a new UpdateManagerInstall.
    *
    * @param string $root
-   *   The app root.
+   *   The root location under which installed projects will be saved.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
    * @param string $site_path
@@ -68,7 +69,7 @@ public function getFormId() {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('app.root'),
+      $container->get('update.root'),
       $container->get('module_handler'),
       $container->get('site.path')
     );
@@ -192,7 +193,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     $project_location = $directory . '/' . $project;
     try {
-      $updater = Updater::factory($project_location);
+      $updater = Updater::factory($project_location, $this->root);
     }
     catch (\Exception $e) {
       drupal_set_message($e->getMessage(), 'error');
@@ -231,7 +232,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     if (fileowner($project_real_location) == fileowner($this->sitePath)) {
       $this->moduleHandler->loadInclude('update', 'inc', 'update.authorize');
       $filetransfer = new Local($this->root);
-      call_user_func_array('update_authorize_run_install', array_merge(array($filetransfer), $arguments));
+      $response = call_user_func_array('update_authorize_run_install', array_merge(array($filetransfer), $arguments));
+      if ($response instanceof Response) {
+        $form_state->setResponse($response);
+      }
     }
 
     // Otherwise, go through the regular workflow to prompt for FTP/SSH
diff --git a/core/modules/update/src/Form/UpdateReady.php b/core/modules/update/src/Form/UpdateReady.php
index 1162c715dfbfa1157f0c2394318af94095a1365c..7ad4d648d510127bd839ea05112e23d202c52828 100644
--- a/core/modules/update/src/Form/UpdateReady.php
+++ b/core/modules/update/src/Form/UpdateReady.php
@@ -14,6 +14,7 @@
 use Drupal\Core\State\StateInterface;
 use Drupal\Core\Updater\Updater;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Configure update settings for this site.
@@ -21,7 +22,7 @@
 class UpdateReady extends FormBase {
 
   /**
-   * The app root.
+   * The root location under which updated projects will be saved.
    *
    * @var string
    */
@@ -52,7 +53,7 @@ class UpdateReady extends FormBase {
    * Constructs a new UpdateReady object.
    *
    * @param string $root
-   *   The app root.
+   *   The root location under which updated projects will be saved.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The object that manages enabled modules in a Drupal installation.
    * @param \Drupal\Core\State\StateInterface $state
@@ -79,7 +80,7 @@ public function getFormId() {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('app.root'),
+      $container->get('update.root'),
       $container->get('module_handler'),
       $container->get('state'),
       $container->get('site.path')
@@ -139,7 +140,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       $project_real_location = NULL;
       foreach ($projects as $project => $url) {
         $project_location = $directory . '/' . $project;
-        $updater = Updater::factory($project_location);
+        $updater = Updater::factory($project_location, $this->root);
         $project_real_location = drupal_realpath($project_location);
         $updates[] = array(
           'project' => $project,
@@ -156,7 +157,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       if (fileowner($project_real_location) == fileowner($this->sitePath)) {
         $this->moduleHandler->loadInclude('update', 'inc', 'update.authorize');
         $filetransfer = new Local($this->root);
-        update_authorize_run_update($filetransfer, $updates);
+        $response = update_authorize_run_update($filetransfer, $updates);
+        if ($response instanceof Response) {
+          $form_state->setResponse($response);
+        }
       }
       // Otherwise, go through the regular workflow to prompt for FTP/SSH
       // credentials and invoke update_authorize_run_update() indirectly with
diff --git a/core/modules/update/src/Tests/UpdateTestBase.php b/core/modules/update/src/Tests/UpdateTestBase.php
index 21a0d21f4b3d2bad6b6f25f14bf621ffe7c710f8..33ee105d87fe7ab4dce78dfd3c66b54b391d6fd0 100644
--- a/core/modules/update/src/Tests/UpdateTestBase.php
+++ b/core/modules/update/src/Tests/UpdateTestBase.php
@@ -21,6 +21,7 @@
 
 namespace Drupal\update\Tests;
 
+use Drupal\Core\DrupalKernel;
 use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
@@ -29,6 +30,29 @@
  */
 abstract class UpdateTestBase extends WebTestBase {
 
+  protected function setUp() {
+    parent::setUp();
+
+    // Change the root path which Update Manager uses to install and update
+    // projects to be inside the testing site directory. See
+    // \Drupal\updateUpdateRootFactory::get() for equivalent changes to the
+    // test child site.
+    $request = \Drupal::request();
+    $update_root = $this->container->get('update.root') . '/' . DrupalKernel::findSitePath($request);
+    $this->container->set('update.root', $update_root);
+    \Drupal::setContainer($this->container);
+
+    // Create the directories within the root path within which the Update
+    // Manager will install projects.
+    foreach (drupal_get_updaters() as $updater_info) {
+      $updater = $updater_info['class'];
+      $install_directory = $update_root . '/' . $updater::getRootDirectoryRelativePath();
+      if (!is_dir($install_directory)) {
+        mkdir($install_directory);
+      }
+    }
+  }
+
   /**
    * Refreshes the update status based on the desired available update scenario.
    *
diff --git a/core/modules/update/src/Tests/UpdateUploadTest.php b/core/modules/update/src/Tests/UpdateUploadTest.php
index 7636f7ab3ab359670235ac723b683852cc39fb46..c568fa5a1b531a9663a2e468efffa9b8bcd6d0f5 100644
--- a/core/modules/update/src/Tests/UpdateUploadTest.php
+++ b/core/modules/update/src/Tests/UpdateUploadTest.php
@@ -46,6 +46,7 @@ public function testUploadModule() {
     // This also checks that the correct archive extensions are allowed.
     $this->drupalPostForm('admin/modules/install', $edit, t('Install'));
     $this->assertText(t('Only files with the following extensions are allowed: @archive_extensions.', array('@archive_extensions' => archiver_get_extensions())),'Only valid archives can be uploaded.');
+    $this->assertUrl('admin/modules/install');
 
     // Check to ensure an existing module can't be reinstalled. Also checks that
     // the archive was extracted since we can't know if the module is already
@@ -56,6 +57,24 @@ public function testUploadModule() {
     );
     $this->drupalPostForm('admin/modules/install', $edit, t('Install'));
     $this->assertText(t('@module_name is already installed.', array('@module_name' => 'AAA Update test')), 'Existing module was extracted and not reinstalled.');
+    $this->assertUrl('admin/modules/install');
+
+    // Ensure that a new module can be extracted and installed.
+    $updaters = drupal_get_updaters();
+    $moduleUpdater = $updaters['module']['class'];
+    $installedInfoFilePath = $this->container->get('update.root') . '/' . $moduleUpdater::getRootDirectoryRelativePath() . '/update_test_new_module/update_test_new_module.info.yml';
+    $this->assertFalse(file_exists($installedInfoFilePath), 'The new module does not exist in the filesystem before it is installed with the Update Manager.');
+    $validArchiveFile = drupal_get_path('module', 'update') . '/tests/update_test_new_module.tar.gz';
+    $edit = array(
+      'files[project_upload]' => $validArchiveFile,
+    );
+    $this->drupalPostForm('admin/modules/install', $edit, t('Install'));
+    // Check that submitting the form takes the user to authorize.php.
+    $this->assertUrl('core/authorize.php');
+    // Check for a success message on the page, and check that the installed
+    // module now exists in the expected place in the filesystem.
+    $this->assertRaw(t('Installed %project_name successfully', array('%project_name' => 'update_test_new_module')));
+    $this->assertTrue(file_exists($installedInfoFilePath), 'The new module exists in the filesystem after it is installed with the Update Manager.');
   }
 
   /**
diff --git a/core/modules/update/src/UpdateRootFactory.php b/core/modules/update/src/UpdateRootFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..60a2201065110cbfa494265d5c0f8aeb8bf977b1
--- /dev/null
+++ b/core/modules/update/src/UpdateRootFactory.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\update\UpdateRootFactory.
+ */
+
+namespace Drupal\update;
+
+use Drupal\Core\DrupalKernelInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Gets the root path used by the Update Manager to install or update projects.
+ */
+class UpdateRootFactory {
+
+  /**
+   * The Drupal kernel.
+   *
+   * @var \Drupal\Core\DrupalKernelInterface
+   */
+  protected $drupalKernel;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs an UpdateRootFactory instance.
+   *
+   * @param \Drupal\Core\DrupalKernelInterface $drupal_kernel
+   *   The Drupal kernel.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *  The request stack.
+   */
+  public function __construct(DrupalKernelInterface $drupal_kernel, RequestStack $request_stack) {
+    $this->drupalKernel = $drupal_kernel;
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * Gets the root path under which projects are installed or updated.
+   *
+   * The Update Manager will ensure that project files can only be copied to
+   * specific subdirectories of this root path.
+   *
+   * @return string
+   */
+  public function get() {
+    // Normally the Update Manager's root path is the same as the app root (the
+    // directory in which the Drupal site is installed).
+    $root_path = $this->drupalKernel->getAppRoot();
+
+    // When running in a test site, change the root path to be the testing site
+    // directory. This ensures that it will always be writable by the webserver
+    // (thereby allowing the actual extraction and installation of projects by
+    // the Update Manager to be tested) and also ensures that new project files
+    // added there won't be visible to the parent site and will be properly
+    // cleaned up once the test finishes running. This is done here (rather
+    // than having the tests enable a module which overrides the update root
+    // factory service) to ensure that the parent site is automatically kept
+    // clean without relying on test authors to take any explicit steps. See
+    // also \Drupal\update\Tests\UpdateTestBase::setUp().
+    if (DRUPAL_TEST_IN_CHILD_SITE) {
+      $kernel = $this->drupalKernel;
+      $request = $this->requestStack->getCurrentRequest();
+      $root_path .= '/' . $kernel::findSitePath($request);
+    }
+
+    return $root_path;
+  }
+
+}
diff --git a/core/modules/update/tests/update_test_new_module.tar.gz b/core/modules/update/tests/update_test_new_module.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..fba2143bf989971d9e1848a7c67b599d0ab15f4e
--- /dev/null
+++ b/core/modules/update/tests/update_test_new_module.tar.gz
@@ -0,0 +1 @@
+‹�ÑwãT�íÔAOÂ0�àû+z#á0Ú²µÆ˜èA^4õJV`‘mÍÖªü{;Æ"& !A‰á}—B»ulogÒÄê±Õµúmœ—©[èApHÌS*nF®b¶9v."ŤJåçy$ã8 ñAÅ®¶IEiV¥sÜOëÿ”Ûžÿöé0+¦e¸Ìû]£	XÊhgþBðÏü…˜2ÉÊ~ç–¿:ñü‹$×ç´÷´Ê›6ySŸ7móî»4~¹ýFR]OªÌج,ü)#gLYÙõ"–mßšn¦Ù,+fa˜dò’ÌüFíyÕU½Úåùæat{G&eå—ÏÂwrìrböë;ì{Uÿ£ÝýçlØõ_(Î}ÿ…dýÿ—fnôû„öéÕ4ó=÷®]ž/7«½.3]ÿQø‡f]úCh,��������������À±}�Ð\¢¥�(��
\ No newline at end of file
diff --git a/core/modules/update/update.authorize.inc b/core/modules/update/update.authorize.inc
index d6a70667414589871221290d1c0c55e3c58e03e6..cead4f027eb99054fe4fafcc40737d15ac9881f4 100644
--- a/core/modules/update/update.authorize.inc
+++ b/core/modules/update/update.authorize.inc
@@ -28,6 +28,11 @@
  *   - updater_name: The name of the Drupal\Core\Updater\Updater class to use
  *     for this project.
  *   - local_url: The locally installed location of new code to update with.
+ *
+ * @return \Symfony\Component\HttpFoundation\Response|null
+ *   The result of processing the batch that updates the projects. If this is
+ *   an instance of \Symfony\Component\HttpFoundation\Response the calling code
+ *   should use that response for the current page request.
  */
 function update_authorize_run_update($filetransfer, $projects) {
   $operations = array();
@@ -50,8 +55,8 @@ function update_authorize_run_update($filetransfer, $projects) {
     'finished' => 'update_authorize_update_batch_finished',
     'file' => drupal_get_path('module', 'update') . '/update.authorize.inc',
   );
-
   batch_set($batch);
+
   // Invoke the batch via authorize.php.
   return system_authorized_batch_process();
 }
@@ -74,6 +79,11 @@ function update_authorize_run_update($filetransfer, $projects) {
  * @param string $local_url
  *   The URL to the locally installed temp directory where the project has
  *   already been downloaded and extracted into.
+ *
+ * @return \Symfony\Component\HttpFoundation\Response|null
+ *   The result of processing the batch that installs the project. If this is
+ *   an instance of \Symfony\Component\HttpFoundation\Response the calling code
+ *   should use that response for the current page request.
  */
 function update_authorize_run_install($filetransfer, $project, $updater_name, $local_url) {
   $operations[] = array(
@@ -147,7 +157,7 @@ function update_authorize_batch_copy_project($project, $updater_name, $local_url
     return;
   }
 
-  $updater = new $updater_name($local_url);
+  $updater = new $updater_name($local_url, \Drupal::getContainer()->get('update.root'));
 
   try {
     if ($updater->isInstalled()) {
diff --git a/core/modules/update/update.services.yml b/core/modules/update/update.services.yml
index c14b8ccdb7ea10009a8aec82928cc3f95a2774c5..fc176d6f7bed9dbffea5fca8207fc4b9ba6d5173 100644
--- a/core/modules/update/update.services.yml
+++ b/core/modules/update/update.services.yml
@@ -13,3 +13,12 @@ services:
   update.fetcher:
     class: Drupal\update\UpdateFetcher
     arguments: ['@config.factory', '@http_client']
+  update.root:
+    class: SplString
+    factory: update.root.factory:get
+    tags:
+      - { name: parameter_service }
+  update.root.factory:
+    class: Drupal\update\UpdateRootFactory
+    arguments: ['@kernel', '@request_stack']
+    public: false