diff --git a/core/composer.json b/core/composer.json
index ddcd1ce..aa25fa2 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -52,6 +52,7 @@
         "drupal/big_pipe": "self.version",
         "drupal/block": "self.version",
         "drupal/block_content": "self.version",
+        "drupal/block_place": "self.version",
         "drupal/book": "self.version",
         "drupal/breakpoint": "self.version",
         "drupal/ckeditor": "self.version",
diff --git a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
index 261cedf..a012741 100644
--- a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
+++ b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php
@@ -67,17 +67,14 @@ public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryIn
    * {@inheritdoc}
    */
   public function applies(RouteMatchInterface $route_match) {
-    // Check whether the route was configured to use the base page theme.
-    return ($route = $route_match->getRouteObject())
-      && $route->hasOption('_theme')
-      && $route->getOption('_theme') == 'ajax_base_page';
+    return ($ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state')) && !empty($ajax_page_state['theme']) && isset($ajax_page_state['theme_token']);
   }
 
   /**
    * {@inheritdoc}
    */
   public function determineActiveTheme(RouteMatchInterface $route_match) {
-    if (($ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state'))  && !empty($ajax_page_state['theme']) && !empty($ajax_page_state['theme_token'])) {
+    if ($ajax_page_state = $this->requestStack->getCurrentRequest()->request->get('ajax_page_state')) {
       $theme = $ajax_page_state['theme'];
       $token = $ajax_page_state['theme_token'];
 
diff --git a/core/modules/block/src/Controller/BlockLibraryController.php b/core/modules/block/src/Controller/BlockLibraryController.php
index eb176d8..69d18f1 100644
--- a/core/modules/block/src/Controller/BlockLibraryController.php
+++ b/core/modules/block/src/Controller/BlockLibraryController.php
@@ -8,6 +8,7 @@
 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Menu\LocalActionManagerInterface;
 use Drupal\Core\Plugin\Context\LazyContextRepository;
+use Drupal\Core\Routing\RedirectDestinationInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -47,6 +48,13 @@ class BlockLibraryController extends ControllerBase {
   protected $localActionManager;
 
   /**
+   * The redirect destination.
+   *
+   * @var \Drupal\Core\Routing\RedirectDestinationInterface
+   */
+  protected $redirectDestination;
+
+  /**
    * Constructs a BlockLibraryController object.
    *
    * @param \Drupal\Core\Block\BlockManagerInterface $block_manager
@@ -57,12 +65,15 @@ class BlockLibraryController extends ControllerBase {
    *   The current route match.
    * @param \Drupal\Core\Menu\LocalActionManagerInterface $local_action_manager
    *   The local action manager.
+   * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
+   *   The redirect destination.
    */
-  public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager) {
+  public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager, RedirectDestinationInterface $redirect_destination) {
     $this->blockManager = $block_manager;
     $this->routeMatch = $route_match;
     $this->localActionManager = $local_action_manager;
     $this->contextRepository = $context_repository;
+    $this->redirectDestination = $redirect_destination;
   }
 
   /**
@@ -73,7 +84,8 @@ public static function create(ContainerInterface $container) {
       $container->get('plugin.manager.block'),
       $container->get('context.repository'),
       $container->get('current_route_match'),
-      $container->get('plugin.manager.menu.local_action')
+      $container->get('plugin.manager.menu.local_action'),
+      $container->get('redirect.destination')
     );
   }
 
@@ -132,6 +144,10 @@ public function listBlocks(Request $request, $theme) {
       if ($region) {
         $links['add']['query']['region'] = $region;
       }
+      $destination = $this->redirectDestination->get();
+      if ($destination) {
+        $links['add']['query']['destination'] = $destination;
+      }
       $row['operations']['data'] = [
         '#type' => 'operations',
         '#links' => $links,
diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php
index 40db579..969ae63 100644
--- a/core/modules/block/src/Tests/BlockTest.php
+++ b/core/modules/block/src/Tests/BlockTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\block\Entity\Block;
+use Drupal\Core\Url;
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
 
@@ -128,6 +129,46 @@ function testBlockVisibilityListedEmpty() {
   }
 
   /**
+   * Test adding a block from the library page with a destination query string.
+   */
+  function testAddBlockFromLibrary() {
+    $default_theme = $this->config('system.theme')->get('default');
+    $help_url = Url::fromRoute('help.page', ['name' => 'block']);
+    // Set up the request so we land on the block help page after creation.
+    $options = [
+      'query' => [
+        'region' => 'sidebar_first',
+        'destination' => $help_url->toString(),
+      ],
+    ];
+    $url = Url::fromRoute('block.admin_library', ['theme' => $default_theme], $options);
+    $this->drupalGet($url);
+    $block_name = 'system_powered_by_block';
+    $add_url = Url::fromRoute('block.admin_add', ['plugin_id' => $block_name, 'theme' => $default_theme]);
+    $links = $this->xpath('//a[contains(@href, :href)]', [':href' => $add_url->toString()]);
+    $this->assertEqual(1, count($links), 'Found one matching link');
+    list($path, $query_string) = explode('?', $links[0]['href'], 2);
+    parse_str($query_string, $query_parts);
+    $this->assertEqual(t('Place block'), (string) $links[0]);
+    $this->assertEqual($help_url->toString(), $query_parts['destination'], 'Expected destination query string is in href');
+    // Create a random title for the block.
+    $title = $this->randomMachineName(8);
+    $block_id = strtolower($this->randomMachineName(8));
+    $edit = array(
+      'id' => $block_id,
+      'settings[label]' => $title,
+    );
+    // Create the block using the link parsed from the library page.
+    $this->drupalPostForm($this->getAbsoluteUrl($links[0]['href']), $edit, t('Save block'));
+    // Verify that we are redirected according to the original request.
+    $this->assertUrl($help_url);
+    // Ensure that the block was created.
+    /** @var \Drupal\block\BlockInterface $block */
+    $block = Block::load($block_id);
+    $this->assertEqual($title, $block->label(), 'Found the block with expected title.');
+  }
+
+  /**
    * Test configuring and moving a module-define block to specific regions.
    */
   function testBlock() {
diff --git a/core/modules/block_place/block_place.info.yml b/core/modules/block_place/block_place.info.yml
new file mode 100644
index 0000000..8ae7226
--- /dev/null
+++ b/core/modules/block_place/block_place.info.yml
@@ -0,0 +1,8 @@
+name: Place Blocks
+type: module
+description: 'Allow administrators to place blocks from any Drupal page'
+package: Core (Experimental)
+version: VERSION
+core: 8.x
+dependencies:
+  - block
diff --git a/core/modules/block_place/block_place.libraries.yml b/core/modules/block_place/block_place.libraries.yml
new file mode 100644
index 0000000..0671252
--- /dev/null
+++ b/core/modules/block_place/block_place.libraries.yml
@@ -0,0 +1,11 @@
+drupal.block_place:
+  version: VERSION
+  css:
+    theme:
+      css/block-place.css: {}
+
+drupal.block_place.icons:
+  version: VERSION
+  css:
+    theme:
+      css/block-place.icons.theme.css: {}
diff --git a/core/modules/block_place/block_place.module b/core/modules/block_place/block_place.module
new file mode 100644
index 0000000..e3151fd
--- /dev/null
+++ b/core/modules/block_place/block_place.module
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Controls the placement of blocks from all pages.
+ */
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
+
+/**
+ * Implements hook_help().
+ */
+function block_place_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'help.page.block_place':
+      $output = '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Place Blocks module module allows you to place blocks from every page. For more information, see the <a href=":blocks-documentation">online documentation for the Place Blocks module</a>.', [':blocks-documentation' => 'https://www.drupal.org/documentation/modules/block_place/']) . '</p>';
+      $output .= '<h3>' . t('Uses') . '</h3>';
+      $output .= '<p>' . t('Block placement is specific to each theme on your site. This module allows you to place blocks in the context of your content pages') . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_toolbar().
+ */
+function block_place_toolbar() {
+  // Link to the current page with a query parameter.
+  $query = \Drupal::request()->query->all();
+  $wrapper_class = '';
+  $status_class = '';
+  $description = '';
+  if (isset($query['block-place'])) {
+    $status_class = 'active';
+    $wrapper_class = 'is-active';
+    $description = t('Exit Place block mode.');
+    unset($query['block-place']);
+    unset($query['destination']);
+  }
+  else {
+    $status_class = 'inactive';
+    $description = t('Show regions to Place blocks.');
+    $query['block-place'] = '1';
+    // Setting destination is both a work-around for the toolbar "Back to site"
+    // link in escapeAdmin.js and used for the destination after picking a
+    // block.
+    $query['destination'] = Url::fromRoute('<current>')->toString();
+  }
+
+  // Remove on Admin routes.
+  $admin_route = \Drupal::service('router.admin_context')->isAdminRoute();
+  // Remove on Block Demo page.
+  $admin_demo = \Drupal::routeMatch()->getRouteName() === 'block.admin_demo';
+  $access = (\Drupal::currentUser()->hasPermission('administer blocks') && !$admin_route && !$admin_demo);
+
+  // The 'Place Block' tab is a simple link, with no corresponding tray.
+  $items['block_place'] = [
+    '#cache' => [
+      'contexts' => ['user.permissions', 'url.query_args'],
+    ],
+    '#type' => 'toolbar_item',
+    'tab' => [
+      '#access' => $access,
+      '#type' => 'link',
+      '#title' => t('Place block'),
+      '#url' => Url::fromRoute('<current>', [], ['query' => $query]),
+      '#attributes' => [
+        'title' => $description,
+        'class' => ['toolbar-icon', 'toolbar-icon-place-block-' . $status_class],
+      ],
+    ],
+    '#wrapper_attributes' => [
+      'class' => ['toolbar-tab', 'block-place-toolbar-tab', $wrapper_class],
+    ],
+    '#weight' => 100,
+    '#attached' => [
+      'library' => [
+        'block_place/drupal.block_place.icons',
+      ],
+   ],
+  ];
+  return $items;
+}
diff --git a/core/modules/block_place/block_place.services.yml b/core/modules/block_place/block_place.services.yml
new file mode 100644
index 0000000..95d7375
--- /dev/null
+++ b/core/modules/block_place/block_place.services.yml
@@ -0,0 +1,6 @@
+services:
+  block_place.page_display_variant_subscriber.block:
+    class: Drupal\block_place\EventSubscriber\BlockPlaceEventSubscriber
+    arguments: ['@request_stack', '@current_user']
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/block_place/css/block-place.css b/core/modules/block_place/css/block-place.css
new file mode 100644
index 0000000..961952f
--- /dev/null
+++ b/core/modules/block_place/css/block-place.css
@@ -0,0 +1,31 @@
+/**
+ * @file
+ * Styling for block_place module regions and buttons during block placement.
+ */
+
+.block-place-region {
+  background: #ffff66;
+  border: 1px dotted #9f9e00;
+  font: 90% "Lucida Grande", "Lucida Sans Unicode", sans-serif;
+  margin: 5px;
+  padding: 5px;
+  text-align: center;
+  text-shadow: none;
+}
+
+.block-place-region a.button {
+  white-space: nowrap;
+  color: #3a3a3a;
+}
+
+.featured-top .block-place-region {
+  font-size: 0.55em;
+}
+.header .block-place-region {
+  width: 500px;
+}
+
+.region:hover {
+  outline: 1px dashed #9f9e00;
+
+}
diff --git a/core/modules/block_place/css/block-place.icons.theme.css b/core/modules/block_place/css/block-place.icons.theme.css
new file mode 100644
index 0000000..10db556
--- /dev/null
+++ b/core/modules/block_place/css/block-place.icons.theme.css
@@ -0,0 +1,20 @@
+/**
+ * @file
+ * Styling for block_place module toolbar icons.
+ */
+.toolbar .block-place-toolbar-tab.is-active {
+  background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
+  background-image: linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
+}
+.toolbar .toolbar-bar .block-place-toolbar-tab {
+  float: right;
+}
+[dir="rtl"] .toolbar .toolbar-bar .block-place-toolbar-tab {
+  float: left;
+}
+.toolbar-bar .toolbar-icon-place-block-active:before {
+  background-image: url(../icons/ffffff/place-block.svg);
+}
+.toolbar-bar .toolbar-icon-place-block-inactive:before {
+  background-image: url(../icons/bebebe/place-block.svg);
+}
diff --git a/core/modules/block_place/icons/bebebe/place-block.svg b/core/modules/block_place/icons/bebebe/place-block.svg
new file mode 100644
index 0000000..a47eb65
--- /dev/null
+++ b/core/modules/block_place/icons/bebebe/place-block.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0" y="0" width="16" height="16" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"><rect x="1" y="6" fill="#bebebe" width="14" height="9"/><ellipse fill="#bebebe" cx="11.48" cy="3.68" rx="2.23" ry="0.61"/><rect x="9.25" y="3.68" fill="#bebebe" width="4.45" height="3.35"/><ellipse fill="#bebebe" cx="4.48" cy="3.68" rx="2.23" ry="0.61"/><rect x="2.25" y="3.68" fill="#bebebe" width="4.45" height="3.35"/></svg>
diff --git a/core/modules/block_place/icons/ffffff/place-block.svg b/core/modules/block_place/icons/ffffff/place-block.svg
new file mode 100644
index 0000000..b83dfd9
--- /dev/null
+++ b/core/modules/block_place/icons/ffffff/place-block.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0" y="0" width="16" height="16" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"><rect x="1" y="6" fill="#FFFFFF" width="14" height="9"/><ellipse fill="#FFFFFF" cx="11.48" cy="3.68" rx="2.23" ry="0.61"/><rect x="9.25" y="3.68" fill="#FFFFFF" width="4.45" height="3.35"/><ellipse fill="#FFFFFF" cx="4.48" cy="3.68" rx="2.23" ry="0.61"/><rect x="2.25" y="3.68" fill="#FFFFFF" width="4.45" height="3.35"/></svg>
diff --git a/core/modules/block_place/src/EventSubscriber/BlockPlaceEventSubscriber.php b/core/modules/block_place/src/EventSubscriber/BlockPlaceEventSubscriber.php
new file mode 100644
index 0000000..da69a00
--- /dev/null
+++ b/core/modules/block_place/src/EventSubscriber/BlockPlaceEventSubscriber.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\block_place\EventSubscriber;
+
+use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
+use Drupal\Core\Render\RenderEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * @see \Drupal\block_place\Plugin\DisplayVariant\PlaceBlockPageVariant
+ */
+class BlockPlaceEventSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * Constructs a \Drupal\block_place\EventSubscriber\BlockPlaceEventSubscriber object.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack used to retrieve the current request.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The current user.
+   */
+  public function __construct(RequestStack $request_stack, AccountInterface $account) {
+    $this->requestStack = $request_stack;
+    $this->account = $account;
+  }
+
+  /**
+   * Selects the block place override of the block page display variant.
+   *
+   * @param \Drupal\Core\Render\PageDisplayVariantSelectionEvent $event
+   *   The event to process.
+   */
+  public function onBlockPageDisplayVariantSelected(PageDisplayVariantSelectionEvent $event) {
+    if ($event->getPluginId() === 'block_page') {
+      if ($this->requestStack->getCurrentRequest()->query->has('block-place') && $this->account->hasPermission('administer blocks')) {
+        $event->setPluginId('block_place_page');
+      }
+      $event->addCacheContexts(['user.permissions', 'url.query_args']);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    // Set a very low priority, so that it runs last.
+    $events[RenderEvents::SELECT_PAGE_DISPLAY_VARIANT][] = ['onBlockPageDisplayVariantSelected', -1000];
+    return $events;
+  }
+
+}
diff --git a/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php b/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php
new file mode 100644
index 0000000..0afc3ee
--- /dev/null
+++ b/core/modules/block_place/src/Plugin/DisplayVariant/PlaceBlockPageVariant.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Drupal\block_place\Plugin\DisplayVariant;
+
+use Drupal\block\BlockRepositoryInterface;
+use Drupal\block\Plugin\DisplayVariant\BlockPageVariant;
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Entity\EntityViewBuilderInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Theme\ThemeManagerInterface;
+use Drupal\Core\Link;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Allows blocks to be placed directly within a region.
+ *
+ * @PageDisplayVariant(
+ *   id = "block_place_page",
+ *   admin_label = @Translation("Page with blocks and place block buttons")
+ * )
+ */
+class PlaceBlockPageVariant extends BlockPageVariant {
+
+  /**
+   * The theme manager.
+   *
+   * @var \Drupal\Core\Theme\ThemeManagerInterface
+   */
+  protected $themeManager;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs a new PlaceBlockPageVariant.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\block\BlockRepositoryInterface $block_repository
+   *   The block repository.
+   * @param \Drupal\Core\Entity\EntityViewBuilderInterface $block_view_builder
+   *   The block view builder.
+   * @param string[] $block_list_cache_tags
+   *   The Block entity type list cache tags.
+   * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
+   *   The theme manager.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The current request stack.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags, ThemeManagerInterface $theme_manager, RequestStack $request_stack) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $block_repository, $block_view_builder, $block_list_cache_tags);
+
+    $this->themeManager = $theme_manager;
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('block.repository'),
+      $container->get('entity_type.manager')->getViewBuilder('block'),
+      $container->get('entity_type.manager')->getDefinition('block')->getListCacheTags(),
+      $container->get('theme.manager'),
+      $container->get('request_stack')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $build = parent::build();
+
+    $active_theme = $this->themeManager->getActiveTheme();
+    $theme_name = $active_theme->getName();
+    $visible_regions = $this->getVisibleRegionNames($theme_name);
+
+    // Build an array of the region names in the right order.
+    $build += array_fill_keys(array_keys($visible_regions), []);
+
+    foreach ($visible_regions as $region => $region_name) {
+      $destination = $this->requestStack->getCurrentRequest()->query->get('destination');
+      $query =  [
+        'region' => $region,
+      ];
+      if ($destination) {
+        $query['destination'] = $destination;
+      }
+      $title = $this->t('Place block<span class="visually-hidden"> in the %region region</span>', ['%region' => $region_name]);
+      $operations['block_description'] = [
+        '#type' => 'inline_template',
+        '#template' => '<div class="block-place-region">{{ link }}</div>',
+        '#context' => [
+          'link' => Link::createFromRoute($title, 'block.admin_library', ['theme' => $theme_name], [
+            'query' => $query,
+            'attributes' => [
+              'title' => $title,
+              'class' => ['use-ajax', 'button', 'button--small'],
+              'data-dialog-type' => 'modal',
+              'data-dialog-options' => Json::encode([
+                'width' => 700,
+              ]),
+            ],
+          ]),
+        ],
+      ];
+      $build[$region] = ['block_place_operations' => $operations] + $build[$region];
+    }
+    $build['#attached']['library'][] = 'block_place/drupal.block_place';
+    return $build;
+  }
+
+  /**
+   * Returns the human-readable list of regions keyed by machine name.
+   *
+   * @param string $theme
+   *   The name of the theme.
+   *
+   * @return array
+   *   An array of human-readable region names keyed by machine name.
+   */
+  protected function getVisibleRegionNames($theme) {
+    return system_region_list($theme, REGIONS_VISIBLE);
+  }
+
+}
diff --git a/core/modules/block_place/src/Tests/BlockPlaceTest.php b/core/modules/block_place/src/Tests/BlockPlaceTest.php
new file mode 100644
index 0000000..93f3ddc
--- /dev/null
+++ b/core/modules/block_place/src/Tests/BlockPlaceTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\block_place\Tests;
+
+use Drupal\Core\Url;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the placing a block.
+ *
+ * @group block_place
+ */
+class BlockPlaceTest extends WebTestBase {
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = ['block', 'block_place', 'toolbar'];
+
+  /**
+   * Tests placing blocks as an admin and anonymous user.
+   */
+  function testPlacingBlocks() {
+    // Create administrative user.
+    $this->drupalLogin($this->drupalCreateUser([
+      'access administration pages',
+      'access toolbar',
+      'administer blocks',
+      'view the administration theme',
+    ]));
+    $this->drupalGet(Url::fromRoute('<front>'));
+    $this->assertLink('Place block');
+    $this->clickLink('Place block');
+
+    // Each region should have one link to place a block.
+    $theme_name = $this->container->get('theme.manager')->getActiveTheme()->getName();
+    $visible_regions = system_region_list($theme_name, REGIONS_VISIBLE);
+    $this->assertTrue(count($visible_regions) > 0, 'At least one region is visible');
+    $default_theme = $this->config('system.theme')->get('default');
+    $block_library_url = Url::fromRoute('block.admin_library', ['theme' => $default_theme]);
+    foreach ($visible_regions as $region => $name) {
+      $block_library_url->setOption('query', ['region' => $region]);
+      $links = $this->xpath('//a[contains(@href, :href)]', [':href' => $block_library_url->toString()]);
+      $this->assertEqual(1, count($links), 'Found one matching link for region ' . $region);
+      list($path, $query_string) = explode('?', $links[0]['href'], 2);
+      parse_str($query_string, $query_parts);
+      $this->assertFalse(empty($query_parts['destination']), 'Destination query string is set in href');
+      /* Get the text inside the div->a->span->em */
+      $demo_block = $this->xpath('//div[@class="block-place-region"]/a[text()="Place block"]//em[text()="' . $name . '"]');
+      $this->assertEqual('1', count($demo_block), 'Found one matching DIV including the region name:  ' . $name);
+    }
+
+    // Create a user who cannot administer blocks.
+    $this->drupalLogin($this->drupalCreateUser([
+      'access administration pages',
+      'access toolbar',
+      'view the administration theme',
+    ]));
+    $this->drupalGet(Url::fromRoute('<front>'));
+    $this->assertNoLink('Place block');
+
+    $this->drupalGet(Url::fromRoute('block.admin_library', ['theme' => 'classy']));
+    $this->assertResponse(403);
+  }
+
+}
