From 3132b889255d4b17bedcfe0563fad66e47852146 Mon Sep 17 00:00:00 2001
From: bcweaver <brianweaver@gmail.com>
Date: Wed, 24 Oct 2018 13:20:13 -0400
Subject: [PATCH] Update 'devel' module to the latest (proper) release

---
 composer.json                                 |   2 +-
 composer.lock                                 |  20 +-
 vendor/composer/installed.json                |  20 +-
 web/modules/devel/css/devel.toolbar.css       |   4 +-
 web/modules/devel/devel.info.yml              |   6 +-
 web/modules/devel/devel.install               |   2 +-
 web/modules/devel/devel.module                |  61 ++--
 web/modules/devel/devel.routing.yml           |  78 ++++--
 .../devel_generate/devel_generate.info.yml    |   6 +-
 .../devel/devel_generate/drush.services.yml   |   7 +
 .../drush/DevelGenerateUnishTest.php          | 151 ----------
 ...te.drush.inc => devel_generate.drush8.inc} |   2 +-
 .../src/Commands/DevelGenerateCommands.php    | 191 +++++++++++++
 .../devel_generate/src/DevelGenerateBase.php  |   4 +
 .../DevelGenerate/ContentDevelGenerate.php    |  30 +-
 .../DevelGenerate/MenuDevelGenerate.php       |  34 ++-
 .../DevelGenerate/TermDevelGenerate.php       |  19 +-
 .../DevelGenerate/UserDevelGenerate.php       |  21 +-
 .../DevelGenerate/VocabularyDevelGenerate.php |   6 +-
 .../devel_generate_example.info.yml           |   6 +-
 web/modules/devel/drush.services.yml          |   6 +
 .../{devel.drush.inc => devel.drush8.inc}     |   4 +-
 web/modules/devel/drush/phpstorm.drush.inc    | 105 -------
 web/modules/devel/kint/kint.info.yml          |   6 +-
 web/modules/devel/log.json                    |  51 ----
 .../devel/src/Commands/DevelCommands.php      | 264 ++++++++++++++++++
 .../devel/src/Controller/DevelController.php  |  31 --
 .../src/Controller/ElementInfoController.php  | 162 +++++++++++
 .../Controller/EntityTypeInfoController.php   | 154 ++++++++++
 .../src/Controller/LayoutInfoController.php   |  91 ++++++
 web/modules/devel/src/DevelDumperBase.php     |   4 +-
 .../ThemeInfoRebuildSubscriber.php            |   4 +
 web/modules/devel/src/Form/ConfigEditor.php   |   2 +-
 .../src/Plugin/Devel/Dumper/DoctrineDebug.php |   5 +
 .../Plugin/Devel/Dumper/DrupalVariable.php    |   7 +-
 .../devel/src/Render/FilteredMarkup.php       |  23 ++
 web/modules/devel/src/ToolbarHandler.php      |   2 +-
 .../devel_dumper_test.info.yml                |   6 +-
 .../devel_entity_test.info.yml                |   6 +-
 .../modules/devel_test/devel_test.info.yml    |   6 +-
 .../src/Functional}/DevelControllerTest.php   |  20 +-
 .../src/Functional}/DevelDumperTest.php       |  60 ++--
 .../src/Functional/DevelElementInfoTest.php   | 151 ++++++++++
 .../Functional/DevelEntityTypeInfoTest.php    | 158 +++++++++++
 .../src/Functional/DevelLayoutInfoTest.php    | 151 ++++++++++
 .../src/Functional}/DevelMenuLinksTest.php    |   6 +-
 .../Functional/DevelModulesReinstallTest.php} |  16 +-
 .../src/Functional/DevelRequirementsTest.php  |   2 +-
 .../src/Functional}/DevelSwitchUserTest.php   |  38 +--
 .../tests/src/Functional/DevelToolbarTest.php |   8 +-
 .../src/Cache/CacheBackendWrapper.php         |   4 +-
 .../webprofiler/src/Compiler/EventPass.php    |  30 --
 .../webprofiler/src/Compiler/ServicePass.php  |   2 -
 .../DataCollector/DatabaseDataCollector.php   |   9 +-
 .../src/DataCollector/EventsDataCollector.php | 129 ++++++---
 .../DataCollector/ServicesDataCollector.php   |   8 +-
 .../Config/ConfigEntityStorageDecorator.php   |   7 +
 .../Config/ShortcutSetStorageDecorator.php    |  12 +-
 .../EventDispatcherTraceableInterface.php     |  19 ++
 .../TraceableEventDispatcher.php              | 193 +++++++++++++
 .../devel/webprofiler/src/Form/ConfigForm.php |   2 +-
 .../webprofiler/src/State/StateWrapper.php    |  17 +-
 .../src/Theme/ThemeNegotiatorWrapper.php      |  43 ++-
 .../src/TraceableEventDispatcher.php          |  70 -----
 .../src/Views/TraceableViewExecutable.php     |   2 +-
 .../src/WebprofilerServiceProvider.php        |  12 +-
 .../templates/Collector/events.html.twig      |  79 +++---
 .../src/FunctionalJavascript/ToolbarTest.php  |  79 ++++++
 .../WebprofilerTestBase.php                   |  80 ++++++
 .../tests/src/Kernel/DecoratorTest.php        |  65 +++++
 .../DataCollector/AssetsDataCollectorTest.php |  68 +++++
 .../CacheDataCollectorTest.php                |   8 +-
 .../DataCollector/DataCollectorBaseTest.php   |  40 +++
 .../devel/webprofiler/webprofiler.info.yml    |   6 +-
 .../devel/webprofiler/webprofiler.routing.yml |   2 +-
 .../webprofiler/webprofiler.services.yml      |   6 +-
 76 files changed, 2419 insertions(+), 792 deletions(-)
 create mode 100644 web/modules/devel/devel_generate/drush.services.yml
 delete mode 100644 web/modules/devel/devel_generate/drush/DevelGenerateUnishTest.php
 rename web/modules/devel/devel_generate/drush/{devel_generate.drush.inc => devel_generate.drush8.inc} (98%)
 create mode 100644 web/modules/devel/devel_generate/src/Commands/DevelGenerateCommands.php
 create mode 100644 web/modules/devel/drush.services.yml
 rename web/modules/devel/drush/{devel.drush.inc => devel.drush8.inc} (98%)
 delete mode 100644 web/modules/devel/drush/phpstorm.drush.inc
 delete mode 100644 web/modules/devel/log.json
 create mode 100644 web/modules/devel/src/Commands/DevelCommands.php
 create mode 100644 web/modules/devel/src/Controller/ElementInfoController.php
 create mode 100644 web/modules/devel/src/Controller/EntityTypeInfoController.php
 create mode 100644 web/modules/devel/src/Controller/LayoutInfoController.php
 create mode 100644 web/modules/devel/src/Render/FilteredMarkup.php
 rename web/modules/devel/{src/Tests => tests/src/Functional}/DevelControllerTest.php (89%)
 rename web/modules/devel/{src/Tests => tests/src/Functional}/DevelDumperTest.php (69%)
 create mode 100644 web/modules/devel/tests/src/Functional/DevelElementInfoTest.php
 create mode 100644 web/modules/devel/tests/src/Functional/DevelEntityTypeInfoTest.php
 create mode 100644 web/modules/devel/tests/src/Functional/DevelLayoutInfoTest.php
 rename web/modules/devel/{src/Tests => tests/src/Functional}/DevelMenuLinksTest.php (95%)
 rename web/modules/devel/{src/Tests/DevelReinstallTest.php => tests/src/Functional/DevelModulesReinstallTest.php} (67%)
 rename web/modules/devel/{src/Tests => tests/src/Functional}/DevelSwitchUserTest.php (92%)
 delete mode 100644 web/modules/devel/webprofiler/src/Compiler/EventPass.php
 create mode 100644 web/modules/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php
 create mode 100644 web/modules/devel/webprofiler/src/EventDispatcher/TraceableEventDispatcher.php
 delete mode 100644 web/modules/devel/webprofiler/src/TraceableEventDispatcher.php
 create mode 100644 web/modules/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php
 create mode 100644 web/modules/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php
 create mode 100644 web/modules/devel/webprofiler/tests/src/Kernel/DecoratorTest.php
 create mode 100644 web/modules/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php
 rename web/modules/devel/webprofiler/tests/src/Unit/{Cache => DataCollector}/CacheDataCollectorTest.php (91%)
 create mode 100644 web/modules/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php

diff --git a/composer.json b/composer.json
index 59a6b18bb0..0f8ba0f8d0 100644
--- a/composer.json
+++ b/composer.json
@@ -88,7 +88,7 @@
         "drupal/crop": "2.0-rc1",
         "drupal/ctools": "3.0",
         "drupal/ctools_views": "3.0",
-        "drupal/devel": "1.0-rc2",
+        "drupal/devel": "1.2",
         "drupal/draggableviews": "1.0",
         "drupal/dropzonejs": "2.0-alpha3",
         "drupal/editor_advanced_link": "1.4",
diff --git a/composer.lock b/composer.lock
index d3672b8c41..fb3a413bab 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "78820edba785cf9a24da44d934c1f8c6",
+    "content-hash": "d5a4707da1701314441aa4c244af663c",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -2832,17 +2832,17 @@
         },
         {
             "name": "drupal/devel",
-            "version": "1.0.0-rc2",
+            "version": "1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupal.org/project/devel",
-                "reference": "8.x-1.0-rc2"
+                "reference": "8.x-1.2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/devel-8.x-1.0-rc2.zip",
-                "reference": "8.x-1.0-rc2",
-                "shasum": "011322bf9c263dab29f94640829f4b654e9c5632"
+                "url": "https://ftp.drupal.org/files/projects/devel-8.x-1.2.zip",
+                "reference": "8.x-1.2",
+                "shasum": "01f3349ef75f6e21fceef24be9d3d6506ca29647"
             },
             "require": {
                 "drupal/core": "~8.0"
@@ -2856,11 +2856,11 @@
                     "dev-1.x": "1.x-dev"
                 },
                 "drupal": {
-                    "version": "8.x-1.0-rc2",
-                    "datestamp": "1502732044",
+                    "version": "8.x-1.2",
+                    "datestamp": "1507197844",
                     "security-coverage": {
-                        "status": "not-covered",
-                        "message": "RC releases are not covered by Drupal security advisories."
+                        "status": "covered",
+                        "message": "Covered by Drupal's security advisory policy"
                     }
                 }
             },
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index c1baf9703b..84383c0426 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -2918,18 +2918,18 @@
     },
     {
         "name": "drupal/devel",
-        "version": "1.0.0-rc2",
-        "version_normalized": "1.0.0.0-RC2",
+        "version": "1.2.0",
+        "version_normalized": "1.2.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupal.org/project/devel",
-            "reference": "8.x-1.0-rc2"
+            "reference": "8.x-1.2"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/devel-8.x-1.0-rc2.zip",
-            "reference": "8.x-1.0-rc2",
-            "shasum": "011322bf9c263dab29f94640829f4b654e9c5632"
+            "url": "https://ftp.drupal.org/files/projects/devel-8.x-1.2.zip",
+            "reference": "8.x-1.2",
+            "shasum": "01f3349ef75f6e21fceef24be9d3d6506ca29647"
         },
         "require": {
             "drupal/core": "~8.0"
@@ -2943,11 +2943,11 @@
                 "dev-1.x": "1.x-dev"
             },
             "drupal": {
-                "version": "8.x-1.0-rc2",
-                "datestamp": "1502732044",
+                "version": "8.x-1.2",
+                "datestamp": "1507197844",
                 "security-coverage": {
-                    "status": "not-covered",
-                    "message": "RC releases are not covered by Drupal security advisories."
+                    "status": "covered",
+                    "message": "Covered by Drupal's security advisory policy"
                 }
             }
         },
diff --git a/web/modules/devel/css/devel.toolbar.css b/web/modules/devel/css/devel.toolbar.css
index 4f4ac976a2..c9871bb7bd 100644
--- a/web/modules/devel/css/devel.toolbar.css
+++ b/web/modules/devel/css/devel.toolbar.css
@@ -18,10 +18,10 @@
     float: left;
 }
 
-.toolbar .toolbar-tray-horizontal .menu {
+.toolbar .toolbar-tray-horizontal .toolbar-menu {
     float: left; /* LTR */
 }
-[dir="rtl"] .toolbar .toolbar-tray-horizontal .menu {
+[dir="rtl"] .toolbar .toolbar-tray-horizontal .toolbar-menu {
     float: right;
 }
 
diff --git a/web/modules/devel/devel.info.yml b/web/modules/devel/devel.info.yml
index f0fae7c4e4..fa7fbf8946 100644
--- a/web/modules/devel/devel.info.yml
+++ b/web/modules/devel/devel.info.yml
@@ -7,8 +7,8 @@ configure: devel.admin_settings
 tags:
  - developer
 
-# Information added by Drupal.org packaging script on 2017-04-23
-version: '8.x-1.0-rc2'
+# Information added by Drupal.org packaging script on 2017-10-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'devel'
-datestamp: 1492989248
+datestamp: 1507197848
diff --git a/web/modules/devel/devel.install b/web/modules/devel/devel.install
index a65d61b4bc..b27e9b97b7 100644
--- a/web/modules/devel/devel.install
+++ b/web/modules/devel/devel.install
@@ -16,7 +16,7 @@ function devel_requirements($phase) {
     // https://www.drupal.org/node/2834400.
     $requirements['devel'] = [
       'title' => t('Devel module enabled'),
-      'description' => t('The module provide the access to debug informations, therefore is recommended to disable the Devel module on production sites.'),
+      'description' => t('The Devel module provides access to internal debugging information; therefore it\'s recommended to disable this module on sites in production.'),
       'severity' => REQUIREMENT_INFO,
     ];
   }
diff --git a/web/modules/devel/devel.module b/web/modules/devel/devel.module
index 1b37d13b56..08b93e22f3 100644
--- a/web/modules/devel/devel.module
+++ b/web/modules/devel/devel.module
@@ -26,6 +26,7 @@
 use Drupal\Core\Utility\Error;
 use Drupal\devel\EntityTypeInfo;
 use Drupal\devel\ToolbarHandler;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Implements hook_help().
@@ -74,6 +75,11 @@ function devel_help($route_name, RouteMatchInterface $route_match) {
     case 'devel.state_system_page':
       return '<p>' . t('This is a list of state variables and their values. For more information read online documentation of <a href=":documentation">State API in Drupal 8</a>.', array(':documentation' => "https://www.drupal.org/developing/api/8/state")) . '</p>';
 
+    case 'devel.layout_info':
+      $output = '';
+      $output .= '<p>' . t('Displays layouts available to the site. For a complete overview of the layout system, see the <a href=":url">Layout API documentation</a>.', [':url' => 'https://www.drupal.org/docs/8/api/layout-api']) . '</p>';
+      return $output;
+
   }
 }
 
@@ -104,6 +110,21 @@ function devel_toolbar() {
     ->toolbar();
 }
 
+/**
+ * Implements hook_menu_links_discovered_alter().
+ */
+function devel_menu_links_discovered_alter(&$links) {
+  // Conditionally add the Layouts info menu link.
+  if (\Drupal::moduleHandler()->moduleExists('layout_discovery')) {
+    $links['devel.layout_info'] = [
+      'title' => new TranslatableMarkup('Layouts Info'),
+      'route_name' => 'devel.layout_info',
+      'description' => new TranslatableMarkup('Overview of layouts available to the site.'),
+      'menu_name' => 'devel',
+    ];
+  }
+}
+
 /**
  * Implements hook_local_tasks_alter().
  */
@@ -531,46 +552,6 @@ function ddebug_backtrace($return = FALSE, $pop = 0, $options = DEBUG_BACKTRACE_
   }
 }
 
-/*
- * Migration-related functions.
- */
-
-/**
- * Regenerates the data in node_comment_statistics table.
- * Technique - http://www.artfulsoftware.com/infotree/queries.php?&bw=1280#101
- *
- * @return void
- */
-function devel_rebuild_node_comment_statistics() {
-  // Empty table.
-  db_truncate('node_comment_statistics')->execute();
-
-  // TODO: DBTNG. Ignore keyword is Mysql only? Is only used in the rare case
-  // when two comments on the same node share same timestamp.
-  $sql = "
-    INSERT IGNORE INTO {node_comment_statistics} (nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count) (
-      SELECT c.nid, c.cid, c.created, c.name, c.uid, c2.comment_count FROM {comment} c
-      JOIN (
-        SELECT c.nid, MAX(c.created) AS created, COUNT(*) AS comment_count FROM {comment} c WHERE status = 1 GROUP BY c.nid
-      ) AS c2 ON c.nid = c2.nid AND c.created = c2.created
-    )";
-  db_query($sql, array(':published' => CommentInterface::PUBLISHED));
-
-  // Insert records into the node_comment_statistics for nodes that are missing.
-  $query = db_select('node', 'n');
-  $query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = n.nid');
-  $query->addField('n', 'changed', 'last_comment_timestamp');
-  $query->addField('n', 'uid', 'last_comment_uid');
-  $query->addField('n', 'nid');
-  $query->addExpression('0', 'comment_count');
-  $query->addExpression('NULL', 'last_comment_name');
-  $query->isNull('ncs.comment_count');
-
-  db_insert('node_comment_statistics', array('return' => Database::RETURN_NULL))
-    ->from($query)
-    ->execute();
-}
-
 /**
  * Implements hook_form_FORM_ID_alter().
  *
diff --git a/web/modules/devel/devel.routing.yml b/web/modules/devel/devel.routing.yml
index 6b91709c4f..8d20e90d1b 100644
--- a/web/modules/devel/devel.routing.yml
+++ b/web/modules/devel/devel.routing.yml
@@ -7,7 +7,7 @@ devel.admin_settings:
     _permission: 'administer site configuration'
 
 devel.toolbar.settings_form:
-  path: 'admin/config/development/devel/toolbar'
+  path: '/admin/config/development/devel/toolbar'
   defaults:
     _form: '\Drupal\devel\Form\ToolbarSettingsForm'
     _title: 'Devel Toolbar Settings'
@@ -50,7 +50,7 @@ devel.config_edit:
   path: '/devel/config/edit/{config_name}'
   defaults:
     _form: '\Drupal\devel\Form\ConfigEditor'
-    _title: 'Edit configuration object: !config_name'
+    _title: 'Edit configuration object: @config_name'
   options:
     _admin_route: TRUE
   requirements:
@@ -86,16 +86,6 @@ devel.theme_registry:
   requirements:
     _permission: 'access devel information'
 
-devel.entity_info_page:
-  path: '/devel/entity/info'
-  defaults:
-    _controller: '\Drupal\devel\Controller\DevelController::entityInfoPage'
-    _title: 'Entity info'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _permission: 'access devel information'
-
 devel.field_info_page:
   path: '/devel/field/info'
   defaults:
@@ -138,16 +128,6 @@ devel.switch:
     _permission: 'switch users'
     _csrf_token: 'TRUE'
 
-devel.elements_page:
-  path: '/devel/elements'
-  defaults:
-    _controller: '\Drupal\devel\Controller\DevelController::elementsPage'
-    _title: 'Element Info'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _permission: 'access devel information'
-
 devel.cache_clear:
   path: '/devel/cache/clear'
   defaults:
@@ -242,3 +222,57 @@ devel.event_info:
     _admin_route: TRUE
   requirements:
     _permission: 'access devel information'
+
+# Layouts info
+devel.layout_info:
+  path: '/devel/layouts'
+  defaults:
+    _controller: '\Drupal\devel\Controller\LayoutInfoController::layoutInfoPage'
+    _title: 'Layouts'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _permission: 'access devel information'
+    _module_dependencies: 'layout_discovery'
+
+# Element info
+devel.elements_page:
+  path: '/devel/elements'
+  defaults:
+    _controller: '\Drupal\devel\Controller\ElementInfoController::elementList'
+    _title: 'Element Info'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _permission: 'access devel information'
+
+devel.elements_page.detail:
+  path: '/devel/elements/{element_name}'
+  defaults:
+    _controller: '\Drupal\devel\Controller\ElementInfoController::elementDetail'
+    _title: 'Element @element_name'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _permission: 'access devel information'
+
+# Entity type info
+devel.entity_info_page:
+  path: '/devel/entity/info'
+  defaults:
+    _controller: '\Drupal\devel\Controller\EntityTypeInfoController::entityTypeList'
+    _title: 'Entity info'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _permission: 'access devel information'
+
+devel.entity_info_page.detail:
+  path: '/devel/entity/info/{entity_type_id}'
+  defaults:
+    _controller: '\Drupal\devel\Controller\EntityTypeInfoController::entityTypeDetail'
+    _title: 'Entity type @entity_type_id'
+  options:
+    _admin_route: TRUE
+  requirements:
+    _permission: 'access devel information'
diff --git a/web/modules/devel/devel_generate/devel_generate.info.yml b/web/modules/devel/devel_generate/devel_generate.info.yml
index 76f572b775..890261d985 100644
--- a/web/modules/devel/devel_generate/devel_generate.info.yml
+++ b/web/modules/devel/devel_generate/devel_generate.info.yml
@@ -6,8 +6,8 @@ package: Development
 tags:
  - developer
 
-# Information added by Drupal.org packaging script on 2017-04-23
-version: '8.x-1.0-rc2'
+# Information added by Drupal.org packaging script on 2017-10-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'devel'
-datestamp: 1492989248
+datestamp: 1507197848
diff --git a/web/modules/devel/devel_generate/drush.services.yml b/web/modules/devel/devel_generate/drush.services.yml
new file mode 100644
index 0000000000..e008b6c172
--- /dev/null
+++ b/web/modules/devel/devel_generate/drush.services.yml
@@ -0,0 +1,7 @@
+services:
+  develgenerate.command:
+    class: Drupal\devel_generate\Commands\DevelGenerateCommands
+    arguments: ['@plugin.manager.develgenerate']
+    tags:
+      -  { name: drush.command }
+
diff --git a/web/modules/devel/devel_generate/drush/DevelGenerateUnishTest.php b/web/modules/devel/devel_generate/drush/DevelGenerateUnishTest.php
deleted file mode 100644
index 128c2773c1..0000000000
--- a/web/modules/devel/devel_generate/drush/DevelGenerateUnishTest.php
+++ /dev/null
@@ -1,151 +0,0 @@
-<?php
-
-namespace Unish;
-
-if (class_exists('Unish\CommandUnishTestCase')) {
-
-  /**
-   * Tests for devel_generate drush commands.
-   *
-   * @group devel_generate
-   */
-  class DevelGenerateUnishTest extends CommandUnishTestCase {
-
-    /**
-     * {@inheritdoc}
-     */
-    public function setUp() {
-      if (UNISH_DRUPAL_MAJOR_VERSION < 8) {
-        $this->markTestSkipped('Devel Generate Tests only available on D8+.');
-      }
-
-      if (!$this->getSites()) {
-        $this->setUpDrupal(1, TRUE, UNISH_DRUPAL_MAJOR_VERSION, 'standard');
-
-        // Symlink the devel module into the sandbox.
-        $devel_directory = dirname(dirname(__DIR__));
-        symlink($devel_directory, $this->webroot() . '/modules/devel');
-
-        // Enable the devel_generate modules.
-        $this->drush('pm-enable', ['devel_generate'], $this->getOptions());
-      }
-
-    }
-
-    /**
-     * Tests devel generate terms.
-     */
-    public function testDevelGenerateTerms() {
-      $this->drush('pm-enable', ['taxonomy'], $this->getOptions());
-
-      $this->drush('generate-terms', [], $this->getOptions(), NULL, NULL, static::EXIT_ERROR);
-      $this->assertContains('Please provide a vocabulary machine name.', $this->getErrorOutput());
-
-      $this->drush('generate-terms', ['unknown'], $this->getOptions(), NULL, NULL, static::EXIT_ERROR);
-      $this->assertContains('Invalid vocabulary name: unknown', $this->getErrorOutput());
-
-      $this->drush('generate-terms', ['tags', 'NaN'], $this->getOptions(), NULL, NULL, static::EXIT_ERROR);
-      $this->assertContains('Invalid number of terms: NaN', $this->getErrorOutput());
-
-      $eval_term_count = "return \\Drupal::entityQuery('taxonomy_term')->count()->execute();";
-      $eval_options = $this->getOptions() + ['format' => 'string'];
-
-      $this->drush('generate-terms', ['tags'], $this->getOptions());
-      $this->assertContains('Created the following new terms:', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_term_count], $eval_options);
-      $this->assertEquals(10, $this->getOutput());
-
-      $this->drush('generate-terms', ['tags', '1'], $this->getOptions());
-      $this->assertContains('Created the following new terms:', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_term_count], $eval_options);
-      $this->assertEquals(11, $this->getOutput());
-
-      $this->drush('generate-terms', ['tags', '1'], $this->getOptions(TRUE));
-      $this->assertContains('Deleted existing terms.', $this->getErrorOutput());
-      $this->assertContains('Created the following new terms:', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_term_count], $eval_options);
-      $this->assertEquals(1, $this->getOutput());
-
-      $this->drush('gent', ['tags', '1'], $this->getOptions());
-      $this->assertContains('Created the following new terms:', $this->getErrorOutput());
-    }
-
-    /**
-     * Tests devel generate contents.
-     */
-    public function testDevelGenerateContents() {
-      $this->drush('pm-enable', ['node'], $this->getOptions());
-
-      $eval_content_count = "return \\Drupal::entityQuery('node')->count()->execute();";
-      $eval_options = $this->getOptions() + ['format' => 'string'];
-
-      // Try to generate 10 content of type "page" or "article"
-      $this->drush('generate-content', [10], $this->getOptions(), NULL, NULL, static::EXIT_SUCCESS);
-      $this->assertContains('Finished creating 10 nodes', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_content_count], $eval_options);
-      $this->assertEquals(10, $this->getOutput());
-
-      // Try to generate 1 content of type "page" or "article"
-      $this->drush('generate-content', [1], $this->getOptions(), NULL, NULL, static::EXIT_SUCCESS);
-      $this->assertContains('1 node created.', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_content_count], $eval_options);
-      $this->assertEquals(11, $this->getOutput());
-
-      // Try to generate 5 content of type "page" or "article", removing all
-      // previous contents.
-      $this->drush('generate-content', [5], $this->getOptions(TRUE), NULL, NULL, static::EXIT_SUCCESS);
-      $this->assertContains('Finished creating 5 nodes', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_content_count], $eval_options);
-      $this->assertEquals(5, $this->getOutput());
-
-      // Try to generate other 5 content with "crappy" type. Output should
-      // remains 5.
-      $generate_content_wrong_ct = $this->getOptions(TRUE) + ['types' => 'crappy'];
-      $this->drush('generate-content', [5], $generate_content_wrong_ct, NULL, NULL, static::EXIT_ERROR);
-      $this->assertContains('One or more content types have been entered that don', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_content_count], $eval_options);
-      $this->assertEquals(5, $this->getOutput());
-
-      // Try to generate other 5 content with empty types. Output should
-      // remains 5.
-      $generate_content_no_types = $this->getOptions(TRUE) + ['types' => ''];
-      $this->drush('generate-content', [5], $generate_content_no_types, NULL, NULL, static::EXIT_ERROR);
-      $this->assertContains('No content types available', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_content_count], $eval_options);
-      $this->assertEquals(5, $this->getOutput());
-
-      // Try to generate other 5 content without any types. Output should
-      // remains 5.
-      $generate_content_no_types = $this->getOptions(TRUE) + ['types' => NULL];
-      $this->drush('generate-content', [5], $generate_content_no_types, NULL, NULL, static::EXIT_ERROR);
-      $this->assertContains('Wrong syntax or no content type selected. The correct syntax uses', $this->getErrorOutput());
-      $this->drush('php-eval', [$eval_content_count], $eval_options);
-      $this->assertEquals(5, $this->getOutput());
-    }
-
-    /**
-     * Default drush options.
-     *
-     * @param bool $kill
-     *   Whether add kill option.
-     *
-     * @return array
-     *   An array containing the default options for drush commands.
-     */
-    protected function getOptions($kill = FALSE) {
-      $options = [
-        'yes' => NULL,
-        'root' => $this->webroot(),
-        'uri' => key($this->getSites()),
-      ];
-
-      if($kill) {
-        $options['kill'] = NULL;
-      }
-
-      return $options;
-    }
-
-  }
-
-}
diff --git a/web/modules/devel/devel_generate/drush/devel_generate.drush.inc b/web/modules/devel/devel_generate/drush/devel_generate.drush8.inc
similarity index 98%
rename from web/modules/devel/devel_generate/drush/devel_generate.drush.inc
rename to web/modules/devel/devel_generate/drush/devel_generate.drush8.inc
index 33ae8a2674..e28226d83a 100644
--- a/web/modules/devel/devel_generate/drush/devel_generate.drush.inc
+++ b/web/modules/devel/devel_generate/drush/devel_generate.drush8.inc
@@ -2,7 +2,7 @@
 
 /**
  * @file
- *  Generate content, taxonomy, menu, and users via drush framework.
+ *  Integration with Drush8. Drush9 commands are in src/Commands.
  */
 use Drupal\devel_generate\DevelGenerateBaseInterface;
 use Drupal\devel_generate\DevelGeneratePluginManager;
diff --git a/web/modules/devel/devel_generate/src/Commands/DevelGenerateCommands.php b/web/modules/devel/devel_generate/src/Commands/DevelGenerateCommands.php
new file mode 100644
index 0000000000..493023f64a
--- /dev/null
+++ b/web/modules/devel/devel_generate/src/Commands/DevelGenerateCommands.php
@@ -0,0 +1,191 @@
+<?php
+namespace Drupal\devel_generate\Commands;
+
+use Consolidation\AnnotatedCommand\CommandData;
+use Drupal\devel_generate\DevelGenerateBaseInterface;
+use Drush\Commands\DrushCommands;
+
+/**
+ * For commands that are parts of modules, Drush expects to find commandfiles in
+ * __MODULE__/src/Commands, and the namespace is Drupal/__MODULE__/Commands.
+ *
+ * In addition to a commandfile like this one, you need to add a drush.services.yml
+ * in root of your module like this module does.
+ */
+class DevelGenerateCommands extends DrushCommands {
+
+  /**
+   * @var DevelGenerateBaseInterface $manager
+   */
+  protected $manager;
+
+  /**
+   * The plugin instance.
+   *
+   * @var DevelGenerateBaseInterface $instance
+   */
+  protected $pluginInstance;
+
+  /**
+   * The Generate plugin parameters.
+   *
+   * @var array $parameters
+   */
+  protected $parameters;
+
+  /**
+   * DevelGenerateCommands constructor.
+   * @param $manager
+   */
+  public function __construct($manager) {
+    parent::__construct();
+    $this->setManager($manager);
+  }
+
+  /**
+   * @return \Drupal\devel_generate\DevelGenerateBaseInterface
+   */
+  public function getManager() {
+    return $this->manager;
+  }
+
+  /**
+   * @param \Drupal\devel_generate\DevelGenerateBaseInterface $manager
+   */
+  public function setManager($manager) {
+    $this->manager = $manager;
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getPluginInstance() {
+    return $this->pluginInstance;
+  }
+
+  /**
+   * @param mixed $pluginInstance
+   */
+  public function setPluginInstance($pluginInstance) {
+    $this->pluginInstance = $pluginInstance;
+  }
+
+  /**
+   * @return array
+   */
+  public function getParameters() {
+    return $this->parameters;
+  }
+
+  /**
+   * @param array $parameters
+   */
+  public function setParameters($parameters) {
+    $this->parameters = $parameters;
+  }
+
+  /**
+   * Create users.
+   *
+   * @command devel-generate-users
+   * @pluginId user
+   * @param $num Number of users to generate.
+   * @option kill Delete all users before generating new ones.
+   * @option roles A comma delimited list of role IDs for new users. Don't specify 'authenticated'.
+   * @option pass Specify a password to be set for all generated users.
+   * @aliases genu
+   */
+  public function users($num = 50, $options = ['kill' => FALSE, 'roles' => '']) {
+    // @todo pass $options to the plugins.
+    $this->generate();
+  }
+
+  /**
+   * Create terms in specified vocabulary.
+   *
+   * @command devel-generate-terms
+   * @pluginId term
+   * @param $machine_name Vocabulary machine name into which new terms will be inserted.
+   * @param $num Number of terms to generate.
+   * @option kill Delete all terms before generating new ones.
+   * @option feedback An integer representing interval for insertion rate logging.
+   * @validate-entity-load taxonomy_vocabulary machine_name
+   * @aliases gent
+   */
+  public function terms($machine_name, $num = 50, $options = ['feedback' => 1000]) {
+    $this->generate();
+  }
+
+  /**
+   * Create vocabularies.
+   *
+   * @command devel-generate-vocabs
+   * @pluginId vocabulary
+   * @param $num Number of vocabularies to generate.
+   * @option kill Delete all vocabs before generating new ones.
+   * @aliases genv
+   * @validate-module-enabled taxonomy
+   */
+  public function vocabs($num = 1, $options = ['kill' => FALSE]) {
+    $this->generate();
+  }
+
+  /**
+   * Create menus.
+   *
+   * @command devel-generate-menus
+   * @pluginId menu
+   * @param $number_menus Number of menus to generate.
+   * @param $number_links Number of links to generate.
+   * @param $max_depth Max link depth.
+   * @param $max_width Max width of first level of links.
+   * @option kill Delete all content before generating new content.
+   * @aliases genm
+   * @validate-module-enabled menu_link_content
+   */
+  public function menus($number_menus = 2, $number_links = 50, $max_depth = 3, $max_width = 8, $options = ['kill' => FALSE]) {
+    $this->generate();
+  }
+
+  /**
+   * Create content.
+   *
+   * @command devel-generate-content
+   * @pluginId content
+   * @param $num Number of nodes to generate.
+   * @param $max_comments Maximum number of comments to generate.
+   * @option kill Delete all content before generating new content.
+   * @option types A comma delimited list of content types to create. Defaults to page,article.
+   * @option feedback An integer representing interval for insertion rate logging.
+   * @option skip-fields A comma delimited list of fields to omit when generating random values
+   * @option languages A comma-separated list of language codes
+   * @aliases genc
+   * @validate-module-enabled node
+   */
+  public function content($num = 50, $max_comments = 0, $options = ['kill' => FALSE, 'types' => 'page,article', 'feedback' => 1000]) {
+    $this->generate();
+    drush_backend_batch_process();
+  }
+
+
+  /**
+   * @hook validate
+   * @param \Consolidation\AnnotatedCommand\CommandData $commandData
+   * @return \Consolidation\AnnotatedCommand\CommandError|null
+   */
+  public function validate(CommandData $commandData) {
+    $manager = $this->getManager();
+    $args = $commandData->input()->getArguments();
+    $commandName = array_shift($args);
+    /** @var DevelGenerateBaseInterface $instance */
+    $instance = $manager->createInstance($commandData->annotationData()->get('pluginId'), array());
+    $this->setPluginInstance($instance);
+    $parameters = $instance->validateDrushParams($args, $commandData->input()->getOptions());
+    $this->setParameters($parameters);
+  }
+
+  public function generate() {
+    $instance = $this->getPluginInstance();
+    $instance->generate($this->getParameters());
+  }
+}
diff --git a/web/modules/devel/devel_generate/src/DevelGenerateBase.php b/web/modules/devel/devel_generate/src/DevelGenerateBase.php
index c2df234de3..1d38a35208 100644
--- a/web/modules/devel/devel_generate/src/DevelGenerateBase.php
+++ b/web/modules/devel/devel_generate/src/DevelGenerateBase.php
@@ -165,4 +165,8 @@ protected function getRandom() {
     }
     return $this->random;
   }
+
+  protected function isDrush8() {
+    return function_exists('drush_drupal_load_autoloader');
+  }
 }
diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php
index 11d55bff34..34e3e71a6e 100644
--- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php
+++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/ContentDevelGenerate.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\devel_generate\DevelGenerateBase;
 use Drupal\field\Entity\FieldConfig;
+use Drush\Utils\StringUtils;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -315,7 +316,7 @@ private function generateContent($values) {
       $start = time();
       for ($i = 1; $i <= $values['num']; $i++) {
         $this->develGenerateContentAddNode($values);
-        if (function_exists('drush_log') && $i % drush_get_option('feedback', 1000) == 0) {
+        if ($this->isDrush8() && function_exists('drush_log') && $i % drush_get_option('feedback', 1000) == 0) {
           $now = time();
           drush_log(dt('Completed @feedback nodes (@rate nodes/min)', array('@feedback' => drush_get_option('feedback', 1000), '@rate' => (drush_get_option('feedback', 1000) * 60) / ($now - $start))), 'ok');
           $start = $now;
@@ -343,7 +344,7 @@ private function generateBatchContent($values) {
       $operations[] = array('devel_generate_operation', array($this, 'batchContentAddNode', $values));
     }
 
-    // Start the batch.
+    // Set the batch.
     $batch = array(
       'title' => $this->t('Generating Content'),
       'operations' => $operations,
@@ -371,8 +372,8 @@ public function batchContentKill($vars, &$context) {
   /**
    * {@inheritdoc}
    */
-  public function validateDrushParams($args) {
-    $add_language = drush_get_option('languages');
+  public function validateDrushParams($args, $options = []) {
+    $add_language = $this->isDrush8() ? drush_get_option('languages') : $options['languages'];
     if (!empty($add_language)) {
       $add_language = explode(',', str_replace(' ', '', $add_language));
       // Intersect with the enabled languages to make sure the language args
@@ -380,33 +381,33 @@ public function validateDrushParams($args) {
       $values['values']['add_language'] = array_intersect($add_language, array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL)));
     }
 
-    $values['kill'] = drush_get_option('kill');
+    $values['kill'] = $this->isDrush8() ? drush_get_option('kill') : $options['kill'];
     $values['title_length'] = 6;
     $values['num'] = array_shift($args);
     $values['max_comments'] = array_shift($args);
     $all_types = array_keys(node_type_get_names());
     $default_types = array_intersect(array('page', 'article'), $all_types);
-    $selected_types = _convert_csv_to_array(drush_get_option('types', $default_types));
-
-    // Validates the input format for content types option.
-    if (drush_get_option('types', $default_types) === TRUE) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Wrong syntax or no content type selected. The correct syntax uses "=", eg.: --types=page,article'));
+    if ($this->isDrush8()) {
+      $selected_types = _convert_csv_to_array(drush_get_option('types', $default_types));
+    }
+    else {
+      $selected_types = StringUtils::csvToArray($options['types'] ?: $default_types);
     }
 
     if (empty($selected_types)) {
-      return drush_set_error('DEVEL_GENERATE_NO_CONTENT_TYPES', dt('No content types available'));
+      throw new \Exception(dt('No content types available'));
     }
 
     $values['node_types'] = array_combine($selected_types, $selected_types);
     $node_types = array_filter($values['node_types']);
 
     if (!empty($values['kill']) && empty($node_types)) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Please provide content type (--types) in which you want to delete the content.'));
+      throw new \Exception(dt('Please provide content type (--types) in which you want to delete the content.'));
     }
 
     // Checks for any missing content types before generating nodes.
     if (array_diff($node_types, $all_types)) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('One or more content types have been entered that don\'t exist on this site'));
+      throw new \Exception(dt('One or more content types have been entered that don\'t exist on this site'));
     }
 
     return $values;
@@ -437,7 +438,6 @@ protected function contentKill($values) {
   protected function develGenerateContentPreNode(&$results) {
     // Get user id.
     $users = $this->getUsers();
-    $users = array_merge($users, array('0'));
     $results['users'] = $users;
   }
 
@@ -492,7 +492,7 @@ protected function getLangcode($results) {
   }
 
   /**
-   * Retrive 50 uids from the database.
+   * Retrieve 50 uids from the database.
    */
   protected function getUsers() {
     $users = array();
diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php
index 7c961a6c39..6ccacc2954 100644
--- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php
+++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/MenuDevelGenerate.php
@@ -221,14 +221,14 @@ public function generateElements(array $values) {
   /**
    * {@inheritdoc}
    */
-  public function validateDrushParams($args) {
+  public function validateDrushParams($args, $options = []) {
 
     $link_types = array('node', 'front', 'external');
     $values = array(
       'num_menus' => array_shift($args),
       'num_links' => array_shift($args),
-      'kill' => drush_get_option('kill'),
-      'pipe' => drush_get_option('pipe'),
+      'kill' => $this->isDrush8() ? drush_get_option('kill') : $options['kill'],
+      'pipe' => $this->isDrush8() ? drush_get_option('pipe') : $options['pipe'],
       'link_types' => array_combine($link_types, $link_types),
     );
 
@@ -240,16 +240,16 @@ public function validateDrushParams($args) {
     $values['existing_menus']['__new-menu__'] = TRUE;
 
     if ($this->isNumber($values['num_menus']) == FALSE) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of menus'));
+      throw new \Exception(dt('Invalid number of menus'));
     }
     if ($this->isNumber($values['num_links']) == FALSE) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of links'));
+      throw new \Exception(dt('Invalid number of links'));
     }
     if ($this->isNumber($values['max_depth']) == FALSE || $values['max_depth'] > 9 || $values['max_depth'] < 1) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid maximum link depth. Use a value between 1 and 9'));
+      throw new \Exception(dt('Invalid maximum link depth. Use a value between 1 and 9'));
     }
     if ($this->isNumber($values['max_width']) == FALSE || $values['max_width'] < 1) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid maximum menu width. Use a positive numeric value.'));
+      throw new \Exception(dt('Invalid maximum menu width. Use a positive numeric value.'));
     }
 
     return $values;
@@ -300,19 +300,17 @@ protected function deleteMenus() {
   protected function generateMenus($num_menus, $title_length = 12) {
     $menus = array();
 
-    if ($this->moduleHandler->moduleExists('menu_ui')) {
-      for ($i = 1; $i <= $num_menus; $i++) {
-        $name = $this->getRandom()->word(mt_rand(2, max(2, $title_length)));
+    for ($i = 1; $i <= $num_menus; $i++) {
+      $name = $this->getRandom()->word(mt_rand(2, max(2, $title_length)));
 
-        $menu = $this->menuStorage->create(array(
-          'label' => $name,
-          'id' => 'devel-' . Unicode::strtolower($name),
-          'description' => $this->t('Description of @name', array('@name' => $name)),
-        ));
+      $menu = $this->menuStorage->create(array(
+        'label' => $name,
+        'id' => 'devel-' . Unicode::strtolower($name),
+        'description' => $this->t('Description of @name', array('@name' => $name)),
+      ));
 
-        $menu->save();
-        $menus[$menu->id()] = $menu->label();
-      }
+      $menu->save();
+      $menus[$menu->id()] = $menu->label();
     }
 
     return $menus;
diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php
index f7aa39b807..61e6dd4110 100644
--- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php
+++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/TermDevelGenerate.php
@@ -210,15 +210,6 @@ protected function generateTerms($records, $vocabs, $maxlength = 12) {
 
       $max++;
 
-      if (function_exists('drush_log')) {
-        $feedback = drush_get_option('feedback', 1000);
-        if ($i % $feedback == 0) {
-          $now = time();
-          drush_log(dt('Completed @feedback terms (@rate terms/min)', array('@feedback' => $feedback, '@rate' => $feedback * 60 / ($now - $start))), 'ok');
-          $start = $now;
-        }
-      }
-
       // Limit memory usage. Only report first 20 created terms.
       if ($i < 20) {
         $terms[] = $term->label();
@@ -233,7 +224,7 @@ protected function generateTerms($records, $vocabs, $maxlength = 12) {
   /**
    * {@inheritdoc}
    */
-  public function validateDrushParams($args) {
+  public function validateDrushParams($args, $options = []) {
     $vocabulary_name = array_shift($args);
     $number = array_shift($args);
 
@@ -242,21 +233,21 @@ public function validateDrushParams($args) {
     }
 
     if (!$vocabulary_name) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Please provide a vocabulary machine name.'));
+      throw new \Exception(dt('Please provide a vocabulary machine name.'));
     }
 
     if (!$this->isNumber($number)) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of terms: @num', array('@num' => $number)));
+      throw new \Exception(dt('Invalid number of terms: @num', array('@num' => $number)));
     }
 
     // Try to convert machine name to a vocabulary id.
     if (!$vocabulary = $this->vocabularyStorage->load($vocabulary_name)) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid vocabulary name: @name', array('@name' => $vocabulary_name)));
+      throw new \Exception(dt('Invalid vocabulary name: @name', array('@name' => $vocabulary_name)));
     }
 
     $values = [
       'num' => $number,
-      'kill' => drush_get_option('kill'),
+      'kill' => $this->isDrush8() ? drush_get_option('kill') : $options['kill'],
       'title_length' => 12,
       'vids' => [$vocabulary->id()],
     ];
diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php
index 420ef8ff5f..4e7344b89e 100644
--- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php
+++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/UserDevelGenerate.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\devel_generate\DevelGenerateBase;
+use Drush\Utils\StringUtils;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -177,14 +178,26 @@ protected function generateElements(array $values) {
   /**
    * {@inheritdoc}
    */
-  public function validateDrushParams($args) {
+  public function validateDrushParams($args, $options = []) {
     $values = array(
       'num' => array_shift($args),
-      'roles' => drush_get_option('roles') ? explode(',', drush_get_option('roles')) : array(),
-      'kill' => drush_get_option('kill'),
-      'pass' => drush_get_option('pass', NULL),
       'time_range' => 0,
     );
+
+    if ($this->isDrush8()) {
+      $values += [
+        'roles' => explode(',', drush_get_option('roles', '')),
+        'kill' => drush_get_option('kill'),
+        'pass' => drush_get_option('pass', NULL),
+      ];
+    }
+    else {
+      $values += [
+        'roles' => StringUtils::csvToArray($options['roles']),
+        'kill' => $options['kill'],
+        'pass' => $options['pass'],
+      ];
+    }
     return $values;
   }
 
diff --git a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php
index a34a2ff4cf..1a037465ae 100644
--- a/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php
+++ b/web/modules/devel/devel_generate/src/Plugin/DevelGenerate/VocabularyDevelGenerate.php
@@ -158,15 +158,15 @@ protected function generateVocabularies($records, $maxlength = 12) {
   /**
    * {@inheritdoc}
    */
-  public function validateDrushParams($args) {
+  public function validateDrushParams($args, $options = []) {
     $values = array(
       'num' => array_shift($args),
-      'kill' => drush_get_option('kill'),
+      'kill' => $this->isDrush8() ? drush_get_option('kill') : $options['kill'],
       'title_length' => 12,
     );
 
     if ($this->isNumber($values['num']) == FALSE) {
-      return drush_set_error('DEVEL_GENERATE_INVALID_INPUT', dt('Invalid number of vocabularies: @num.', array('@num' => $values['num'])));
+      throw new \Exception(dt('Invalid number of vocabularies: @num.', array('@num' => $values['num'])));
     }
 
     return $values;
diff --git a/web/modules/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml b/web/modules/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml
index b0f5c0abdf..b4acf0a6d1 100644
--- a/web/modules/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml
+++ b/web/modules/devel/devel_generate/tests/modules/devel_generate_example/devel_generate_example.info.yml
@@ -7,8 +7,8 @@ configure: admin/config/development/generate
 tags:
  - developer
 
-# Information added by Drupal.org packaging script on 2017-04-23
-version: '8.x-1.0-rc2'
+# Information added by Drupal.org packaging script on 2017-10-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'devel'
-datestamp: 1492989248
+datestamp: 1507197848
diff --git a/web/modules/devel/drush.services.yml b/web/modules/devel/drush.services.yml
new file mode 100644
index 0000000000..1c8ea0bc52
--- /dev/null
+++ b/web/modules/devel/drush.services.yml
@@ -0,0 +1,6 @@
+services:
+  devel.command:
+    class: Drupal\devel\Commands\DevelCommands
+    arguments: ['@token', '@service_container', '@event_dispatcher', '@module_handler']
+    tags:
+      -  { name: drush.command }
diff --git a/web/modules/devel/drush/devel.drush.inc b/web/modules/devel/drush/devel.drush8.inc
similarity index 98%
rename from web/modules/devel/drush/devel.drush.inc
rename to web/modules/devel/drush/devel.drush8.inc
index b38cf9aef4..651578d3e5 100644
--- a/web/modules/devel/drush/devel.drush.inc
+++ b/web/modules/devel/drush/devel.drush8.inc
@@ -2,7 +2,8 @@
 
 /**
  * @file
- * Drush integration for the devel module.
+ * This file is only used by Drush8. Drush9 discovers its commands via tagged
+ * service(s) in devel.services.yml. Also see classes in src/Commands.
  */
 
 use Drupal\Component\Uuid\Php;
@@ -160,6 +161,7 @@ function drush_devel_fn_event($event = NULL) {
     drush_log(dt('No implementations.'), 'ok');
   }
 }
+
 /**
  * Command handler.  Show source code of specified function or method.
  */
diff --git a/web/modules/devel/drush/phpstorm.drush.inc b/web/modules/devel/drush/phpstorm.drush.inc
deleted file mode 100644
index 416bdd63d2..0000000000
--- a/web/modules/devel/drush/phpstorm.drush.inc
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-
-/**
- * @file
- * Generate PhpStorm metadata file.
- */
-
-/**
- * Implements of hook_drush_command().
- */
-function phpstorm_drush_command() {
-  $items = array();
-
-  $items['phpstorm-metadata'] = array(
-    'description' => 'Save the PhpStorm Metadata file to Drupal root.',
-    'core' => array('8+'),
-    'aliases' => array('phpm'),
-    'category' => 'devel',
-  );
-
-  return $items;
-}
-
-/**
- * Implements hook_drush_help_alter().
- */
-function phpstorm_drush_help_alter(&$command) {
-  if ($command['command'] == 'cache-rebuild') {
-    $command['options']['storm'] = 'Write a new PHPstorm metadata file to Drupal root.';
-  }
-}
-
-/*
- * Implements drush_hook_post_COMMAND().
- */
-function drush_phpstorm_post_cache_rebuild() {
-  if (drush_get_option('storm')) {
-    drush_invoke_process('@self', 'phpstorm-metadata');
-  }
-}
-
-/**
- * Generate PhpStorm Metadata file.
- *
- * @see http://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata
- */
-function drush_phpstorm_metadata() {
-  $container = \Drupal::getContainer();
-
-  $reflectedClass = new ReflectionClass($container);
-
-  $map = array();
-
-  // Map for all services of the container.
-  // @see \Symfony\Component\DependencyInjection\Container::getServiceIds().
-  foreach ($reflectedClass->getMethods() as $method) {
-    if (preg_match('/^get(.+)Service$/', $method->name, $match)) {
-      $id = strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr($match[1], '_', '.')));
-      $service = \Drupal::service($id);
-      if (is_object($service)) {
-        $map["\\Drupal::service('')"][$id] = '\\' . get_class($service);
-      }
-    }
-  }
-
-  // Entity Manager - getStorage
-  foreach (\Drupal::entityTypeManager()->getDefinitions() as $type => $definition) {
-    $class = Drupal::entityTypeManager()->getStorage($type);
-    $map["\\Drupal::entityManager()->getStorage('')"][$type] = '\\' . get_class($class);
-    $map["\\Drupal::entityTypeManager()->getStorage('')"][$type] = '\\' . get_class($class);
-  }
-
-  $content = _drush_phpstorm_metadata_phpstorm_metadata_template($map);
-  file_put_contents(DRUPAL_ROOT . '/.phpstorm.meta.php', $content);
-}
-
-function _drush_phpstorm_metadata_phpstorm_metadata_template($data) {
-  $file = '<?php
-
-namespace PHPSTORM_META {
-
-  /** @noinspection PhpUnusedLocalVariableInspection */
-  /** @noinspection PhpIllegalArrayKeyTypeInspection */
-  $STATIC_METHOD_TYPES = [
-';
-
-  foreach ($data as $method => $map) {
-    $file .= "\n";
-    $file .= "    {$method} => [\n";
-
-    foreach ($map as $argument => $class) {
-      $file .= "      '{$argument}' instanceof {$class},\n";
-    }
-
-    $file .= "    ],";
-    $file .= "\n";
-  }
-
-  $file .= '
-    ];
-  }
-  ';
-
-  return $file;
-}
diff --git a/web/modules/devel/kint/kint.info.yml b/web/modules/devel/kint/kint.info.yml
index 658553d44e..0b20df64b7 100644
--- a/web/modules/devel/kint/kint.info.yml
+++ b/web/modules/devel/kint/kint.info.yml
@@ -6,8 +6,8 @@ package: Development
 tags:
  - developer
 
-# Information added by Drupal.org packaging script on 2017-04-23
-version: '8.x-1.0-rc2'
+# Information added by Drupal.org packaging script on 2017-10-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'devel'
-datestamp: 1492989248
+datestamp: 1507197848
diff --git a/web/modules/devel/log.json b/web/modules/devel/log.json
deleted file mode 100644
index e9987b4b28..0000000000
--- a/web/modules/devel/log.json
+++ /dev/null
@@ -1,51 +0,0 @@
-{"level":"debug","msg":"main.go started","time":"2017-03-24T19:33:53+01:00"}
-{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:33:53+01:00"}
-{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:33:53+01:00"}
-{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:33:53+01:00"}
-{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:33:53+01:00"}
-{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:33:53+01:00"}
-{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:33:53+01:00"}
-{"level":"debug","msg":"app.Stop(drupal8)","time":"2017-03-24T19:33:53+01:00"}
-{"level":"error","msg":"open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:33:53+01:00"}
-{"level":"error","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:33:53+01:00"}
-{"level":"fatal","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:33:53+01:00"}
-{"level":"debug","msg":"main.go started","time":"2017-03-24T19:33:58+01:00"}
-{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:33:58+01:00"}
-{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:33:58+01:00"}
-{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:33:58+01:00"}
-{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:33:58+01:00"}
-{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:33:58+01:00"}
-{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:33:58+01:00"}
-{"level":"debug","msg":"app.Stop(d8)","time":"2017-03-24T19:33:58+01:00"}
-{"level":"debug","msg":"main.go started","time":"2017-03-24T19:44:13+01:00"}
-{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:44:13+01:00"}
-{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:44:13+01:00"}
-{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:44:13+01:00"}
-{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:44:13+01:00"}
-{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:44:13+01:00"}
-{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:44:13+01:00"}
-{"level":"debug","msg":"app.Start(drupal8)","time":"2017-03-24T19:44:13+01:00"}
-{"level":"error","msg":"open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:13+01:00"}
-{"level":"error","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:13+01:00"}
-{"level":"fatal","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:13+01:00"}
-{"level":"debug","msg":"main.go started","time":"2017-03-24T19:44:17+01:00"}
-{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:44:17+01:00"}
-{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:44:17+01:00"}
-{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:44:17+01:00"}
-{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:44:17+01:00"}
-{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:44:17+01:00"}
-{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:44:17+01:00"}
-{"level":"debug","msg":"app.Start(drupal8builder)","time":"2017-03-24T19:44:17+01:00"}
-{"level":"error","msg":"open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:17+01:00"}
-{"level":"error","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:17+01:00"}
-{"level":"fatal","msg":"Error in Docker project creation: open /docker-compose.yml: no such file or directory","time":"2017-03-24T19:44:17+01:00"}
-{"level":"debug","msg":"main.go started","time":"2017-03-24T19:44:25+01:00"}
-{"level":"info","msg":"Preflight(dockerMachinePath) = /usr/local/bin/docker-machine","time":"2017-03-24T19:44:25+01:00"}
-{"level":"info","msg":" Preflight(dockerMachineInstalled) = true","time":"2017-03-24T19:44:25+01:00"}
-{"level":"info","msg":"Preflight(dockerPath) = /usr/local/bin/docker","time":"2017-03-24T19:44:25+01:00"}
-{"level":"info","msg":" Preflight(dockerInstalled) = true","time":"2017-03-24T19:44:25+01:00"}
-{"level":"info","msg":"Preflight(socket) = tcp://10.211.55.8:2376","time":"2017-03-24T19:44:25+01:00"}
-{"level":"info","msg":" Preflight(dockerIsUp) = true","time":"2017-03-24T19:44:25+01:00"}
-{"level":"debug","msg":"app.Start(d8builder)","time":"2017-03-24T19:44:25+01:00"}
-{"level":"info","msg":"- Project:d8builderstatus is: STARTED","time":"2017-03-24T19:44:28+01:00"}
-{"level":"debug","msg":"Show(d8builder)","time":"2017-03-24T19:44:28+01:00"}
diff --git a/web/modules/devel/src/Commands/DevelCommands.php b/web/modules/devel/src/Commands/DevelCommands.php
new file mode 100644
index 0000000000..cda3e1a01f
--- /dev/null
+++ b/web/modules/devel/src/Commands/DevelCommands.php
@@ -0,0 +1,264 @@
+<?php
+namespace Drupal\devel\Commands;
+use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
+use Drupal\Component\Uuid\Php;
+use Drupal\Core\Utility\Token;
+use Drush\Commands\DrushCommands;
+use Drush\Exceptions\UserAbortException;
+use Drush\Utils\StringUtils;
+use Symfony\Component\Console\Input\Input;
+use Symfony\Component\Console\Output\Output;
+
+/**
+ * For commands that are parts of modules, Drush expects to find commandfiles in
+ * __MODULE__/src/Commands, and the namespace is Drupal/__MODULE__/Commands.
+ *
+ * In addition to a commandfile like this one, you need to add a drush.services.yml
+ * in root of your module like this module does.
+ */
+class DevelCommands extends DrushCommands {
+
+  protected $token;
+
+  protected $container;
+
+  protected $eventDispatcher;
+
+  protected $moduleHandler;
+
+  public function __construct(Token $token, $container, $eventDispatcher, $moduleHandler) {
+    parent::__construct();
+    $this->token = $token;
+    $this->container = $container;
+    $this->eventDispatcher = $eventDispatcher;
+    $this->moduleHandler = $moduleHandler;
+  }
+
+  /**
+   * @return \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  public function getModuleHandler() {
+    return $this->moduleHandler;
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getEventDispatcher() {
+    return $this->eventDispatcher;
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getContainer() {
+    return $this->container;
+  }
+
+  /**
+   * @return Token
+   */
+  public function getToken() {
+    return $this->token;
+  }
+
+  /**
+   * Uninstall, and Install modules.
+
+   * @command devel:reinstall
+   * @param $modules A comma-separated list of module names.
+   * @aliases dre,devel-reinstall
+   * @allow-additional-options pm-uninstall,pm-enable
+   */
+  public function reinstall($modules) {
+    $modules = StringUtils::csvToArray($modules);
+
+    $modules_str = implode(',', $modules);
+    drush_invoke_process('@self', 'pm:uninstall', [$modules_str], []);
+    drush_invoke_process('@self', 'pm:enable', [$modules_str], []);
+  }
+
+  /**
+   * List implementations of a given hook and optionally edit one.
+   *
+   * @command devel:hook
+   * @param $hook The name of the hook to explore.
+   * @param $implementation The name of the implementation to edit. Usually omitted.
+   * @usage devel-hook cron
+   *   List implementations of hook_cron().
+   * @aliases fnh,fn-hook,hook,devel-hook
+   * @optionset_get_editor
+   */
+  function hook($hook, $implementation) {
+    // Get implementations in the .install files as well.
+    include_once './core/includes/install.inc';
+    drupal_load_updates();
+    $info = $this->codeLocate($implementation . "_$hook");
+    $exec = drush_get_editor();
+    drush_shell_exec_interactive($exec, $info['file']);
+  }
+
+  /**
+   * @hook interact hook
+   */
+  public function hookInteract(Input $input, Output $output) {
+    if (!$input->getArgument('implementation')) {
+      if ($hook_implementations = $this->getModuleHandler()->getImplementations($input->getArgument('hook'))) {
+        if (!$choice = $this->io()->choice('Enter the number of the hook implementation you wish to view.', array_combine($hook_implementations, $hook_implementations))) {
+          throw new UserAbortException();
+        }
+        $input->setArgument('implementation', $choice);
+      }
+      else {
+        throw new \Exception(dt('No implementations'));
+      }
+    }
+  }
+
+  /**
+   * List implementations of a given event and optionally edit one.
+   *
+   * @command devel:event
+   * @param $event The name of the event to explore. If omitted, a list of events is shown.
+   * @param $implementation The name of the implementation to show. Usually omitted.
+   * @usage devel-event
+   *   Pick a Kernel event, then pick an implementation, and then view its source code.
+   * @usage devel-event kernel.terminate
+   *   Pick a terminate subscribers implementation and view its source code.
+   * @aliases fne,fn-event,event
+   */
+  function event($event, $implementation) {
+    $info= $this->codeLocate($implementation);
+    $exec = drush_get_editor();
+    drush_shell_exec_interactive($exec, $info['file']);
+  }
+
+  /**
+   * @hook interact devel:event
+   */
+  public function interactEvent(Input $input, Output $output) {
+    $dispatcher = $this->getEventDispatcher();
+    if (!$input->getArgument('event')) {
+      // @todo Expand this list.
+      $events = array('kernel.controller', 'kernel.exception', 'kernel.request', 'kernel.response', 'kernel.terminate', 'kernel.view');
+      $events = array_combine($events, $events);
+      if (!$event = $this->io()->choice('Enter the event you wish to explore.', $events)) {
+        throw new UserAbortException();
+      }
+      $input->setArgument('event', $event);
+    }
+    if ($implementations = $dispatcher->getListeners($event)) {
+      foreach ($implementations as $implementation) {
+        $callable = get_class($implementation[0]) . '::' . $implementation[1];
+        $choices[$callable] = $callable;
+      }
+      if (!$choice = $this->io()->choice('Enter the number of the implementation you wish to view.', $choices)) {
+        throw new UserAbortException();
+      }
+      $input->setArgument('implementation', $choice);
+    }
+    else {
+      throw new \Exception(dt('No implementations.'));
+    }
+  }
+
+  /**
+   * List available tokens.
+   *
+   * @command devel:token
+   * @aliases token,devel-token
+   * @field-labels
+   *   group: Group
+   *   token: Token
+   *   name: Name
+   * @default-fields group,token,name
+   *
+   * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
+   */
+  public function token($options = ['format' => 'table']) {
+    $all = $this->getToken()->getInfo();
+    foreach ($all['tokens'] as $group => $tokens) {
+      foreach ($tokens as $key => $token) {
+        $rows[] = [
+          'group' => $group,
+          'token' => $key,
+          'name' => $token['name'],
+        ];
+      }
+    }
+    return new RowsOfFields($rows);
+  }
+
+  /**
+   * Generate a UUID.
+   *
+   * @command devel:uuid
+   * @aliases uuid,devel-uuid
+   * @usage drush devel-uuid
+   *   Outputs a Universally Unique Identifier.
+   *
+   * @return string
+   */
+  public function uuid() {
+    $uuid = new Php();
+    return $uuid->generate();
+  }
+
+
+  /**
+   * Get source code line for specified function or method.
+   */
+  function codeLocate($function_name) {
+    // Get implementations in the .install files as well.
+    include_once './core/includes/install.inc';
+    drupal_load_updates();
+
+    if (strpos($function_name, '::') === FALSE) {
+      if (!function_exists($function_name)) {
+        throw new \Exception(dt('Function not found'));
+      }
+      $reflect = new \ReflectionFunction($function_name);
+    }
+    else {
+      list($class, $method) = explode('::', $function_name);
+      if (!method_exists($class, $method)) {
+        throw new \Exception(dt('Method not found'));
+      }
+      $reflect = new \ReflectionMethod($class, $method);
+    }
+    return array('file' => $reflect->getFileName(), 'startline' => $reflect->getStartLine(), 'endline' => $reflect->getEndLine());
+
+  }
+
+  /**
+   * Get a list of available container services.
+   *
+   * @command devel:services
+   * @param $prefix A prefix to filter the service list by.
+   * @aliases devel-container-services,dcs,devel-services
+   * @usage drush devel-services
+   *   Gets a list of all available container services
+   * @usage drush dcs plugin.manager
+   *   Get all services containing "plugin.manager"
+   *
+   * @return array
+   */
+  public function services($prefix = NULL, $options = ['format' => 'yaml']) {
+    $container = $this->getContainer();
+
+    // Get a list of all available service IDs.
+    $services = $container->getServiceIds();
+
+    // If there is a prefix, try to find matches.
+    if (isset($prefix)) {
+      $services = preg_grep("/$prefix/", $services);
+    }
+
+    if (empty($services)) {
+      throw new \Exception(dt('No container services found.'));
+    }
+
+    sort($services);
+    return $services;
+  }
+}
\ No newline at end of file
diff --git a/web/modules/devel/src/Controller/DevelController.php b/web/modules/devel/src/Controller/DevelController.php
index 92f59270dc..2c3a7b4def 100644
--- a/web/modules/devel/src/Controller/DevelController.php
+++ b/web/modules/devel/src/Controller/DevelController.php
@@ -53,25 +53,6 @@ public function themeRegistry() {
     return $this->dumper->exportAsRenderable($hooks);
   }
 
-  /**
-   * Builds the elements info overview page.
-   *
-   * @return array
-   *   Array of page elements to render.
-   */
-  public function elementsPage() {
-    $element_info_manager = \Drupal::service('element_info');
-
-    $elements_info = array();
-    foreach ($element_info_manager->getDefinitions() as $element_type => $definition) {
-      $elements_info[$element_type] = $definition + $element_info_manager->getInfo($element_type);
-    }
-
-    ksort($elements_info);
-
-    return $this->dumper->exportAsRenderable($elements_info);
-  }
-
   /**
    * Builds the fields info overview page.
    *
@@ -106,18 +87,6 @@ public function fieldInfoPage() {
     return $output;
   }
 
-  /**
-   * Builds the entity types overview page.
-   *
-   * @return array
-   *   Array of page elements to render.
-   */
-  public function entityInfoPage() {
-    $types = $this->entityTypeManager()->getDefinitions();
-    ksort($types);
-    return $this->dumper->exportAsRenderable($types);
-  }
-
   /**
    * Builds the state variable overview page.
    *
diff --git a/web/modules/devel/src/Controller/ElementInfoController.php b/web/modules/devel/src/Controller/ElementInfoController.php
new file mode 100644
index 0000000000..a0440c3276
--- /dev/null
+++ b/web/modules/devel/src/Controller/ElementInfoController.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Drupal\devel\Controller;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Render\ElementInfoManagerInterface;
+use Drupal\Core\Url;
+use Drupal\devel\DevelDumperManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Provides route responses for the element info page.
+ */
+class ElementInfoController extends ControllerBase {
+
+  /**
+   * Element info manager service.
+   *
+   * @var \Drupal\Core\Render\ElementInfoManagerInterface
+   */
+  protected $elementInfo;
+
+  /**
+   * The dumper service.
+   *
+   * @var \Drupal\devel\DevelDumperManagerInterface
+   */
+  protected $dumper;
+
+  /**
+   * EventInfoController constructor.
+   *
+   * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
+   *   Element info manager service.
+   * @param \Drupal\devel\DevelDumperManagerInterface $dumper
+   *   The dumper service.
+   */
+  public function __construct(ElementInfoManagerInterface $element_info, DevelDumperManagerInterface $dumper) {
+    $this->elementInfo = $element_info;
+    $this->dumper = $dumper;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('element_info'),
+      $container->get('devel.dumper')
+    );
+  }
+
+  /**
+   * Builds the element overview page.
+   *
+   * @return array
+   *   A render array as expected by the renderer.
+   */
+  public function elementList() {
+    $headers = [
+      $this->t('Name'),
+      $this->t('Provider'),
+      $this->t('Class'),
+      $this->t('Operations'),
+    ];
+
+    $rows = [];
+
+    foreach ($this->elementInfo->getDefinitions() as $element_type => $definition) {
+      $row['name'] = [
+        'data' => $element_type,
+        'class' => 'table-filter-text-source',
+      ];
+      $row['provider'] = [
+        'data' => $definition['provider'],
+        'class' => 'table-filter-text-source',
+      ];
+      $row['class'] = [
+        'data' => $definition['class'],
+        'class' => 'table-filter-text-source',
+      ];
+      $row['operations']['data'] = [
+        '#type' => 'operations',
+        '#links' => [
+          'devel' => [
+            'title' => $this->t('Devel'),
+            'url' => Url::fromRoute('devel.elements_page.detail', ['element_name' => $element_type]),
+            'attributes' => [
+              'class' => ['use-ajax'],
+              'data-dialog-type' => 'modal',
+              'data-dialog-options' => Json::encode([
+                'width' => 700,
+                'minHeight' => 500,
+              ]),
+            ],
+          ],
+        ],
+      ];
+
+      $rows[$element_type] = $row;
+    }
+
+    ksort($rows);
+
+    $output['#attached']['library'][] = 'system/drupal.system.modules';
+
+    $output['filters'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['table-filter', 'js-show'],
+      ],
+    ];
+    $output['filters']['text'] = [
+      '#type' => 'search',
+      '#title' => $this->t('Search'),
+      '#size' => 30,
+      '#placeholder' => $this->t('Enter element id, provider or class'),
+      '#attributes' => [
+        'class' => ['table-filter-text'],
+        'data-table' => '.devel-filter-text',
+        'autocomplete' => 'off',
+        'title' => $this->t('Enter a part of the element id, provider or class to filter by.'),
+      ],
+    ];
+    $output['elements'] = [
+      '#type' => 'table',
+      '#header' => $headers,
+      '#rows' => $rows,
+      '#empty' => $this->t('No elements found.'),
+      '#sticky' => TRUE,
+      '#attributes' => [
+        'class' => ['devel-element-list', 'devel-filter-text'],
+      ],
+    ];
+
+    return $output;
+  }
+
+  /**
+   * Returns a render array representation of the element.
+   *
+   * @param string $element_name
+   *   The name of the element to retrieve.
+   *
+   * @return array
+   *   A render array containing the element.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   *   If the requested element is not defined.
+   */
+  public function elementDetail($element_name) {
+    if (!$element = $this->elementInfo->getDefinition($element_name, FALSE)) {
+      throw new NotFoundHttpException();
+    }
+
+    $element += $this->elementInfo->getInfo($element_name);
+    return $this->dumper->exportAsRenderable($element, $element_name);
+  }
+
+}
diff --git a/web/modules/devel/src/Controller/EntityTypeInfoController.php b/web/modules/devel/src/Controller/EntityTypeInfoController.php
new file mode 100644
index 0000000000..f8bac6dc00
--- /dev/null
+++ b/web/modules/devel/src/Controller/EntityTypeInfoController.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Drupal\devel\Controller;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Url;
+use Drupal\devel\DevelDumperManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Provides route responses for the entity types info page.
+ */
+class EntityTypeInfoController extends ControllerBase {
+
+  /**
+   * The dumper service.
+   *
+   * @var \Drupal\devel\DevelDumperManagerInterface
+   */
+  protected $dumper;
+
+  /**
+   * EntityTypeInfoController constructor.
+   *
+   * @param \Drupal\devel\DevelDumperManagerInterface $dumper
+   *   The dumper service.
+   */
+  public function __construct(DevelDumperManagerInterface $dumper) {
+    $this->dumper = $dumper;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('devel.dumper')
+    );
+  }
+
+  /**
+   * Builds the entity types overview page.
+   *
+   * @return array
+   *   A render array as expected by the renderer.
+   */
+  public function entityTypeList() {
+    $headers = [
+      $this->t('ID'),
+      $this->t('Name'),
+      $this->t('Provider'),
+      $this->t('Class'),
+      $this->t('Operations'),
+    ];
+
+    $rows = [];
+
+    foreach ($this->entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
+      $row['id'] = [
+        'data' => $entity_type->id(),
+        'class' => 'table-filter-text-source',
+      ];
+      $row['name'] = [
+        'data' => $entity_type->getLabel(),
+        'class' => 'table-filter-text-source',
+      ];
+      $row['provider'] = [
+        'data' => $entity_type->getProvider(),
+        'class' => 'table-filter-text-source',
+      ];
+      $row['class'] = [
+        'data' => $entity_type->getClass(),
+        'class' => 'table-filter-text-source',
+      ];
+      $row['operations']['data'] = [
+        '#type' => 'operations',
+        '#links' => [
+          'devel' => [
+            'title' => $this->t('Devel'),
+            'url' => Url::fromRoute('devel.entity_info_page.detail', ['entity_type_id' => $entity_type_id]),
+            'attributes' => [
+              'class' => ['use-ajax'],
+              'data-dialog-type' => 'modal',
+              'data-dialog-options' => Json::encode([
+                'width' => 700,
+                'minHeight' => 500,
+              ]),
+            ],
+          ],
+        ],
+      ];
+
+      $rows[$entity_type_id] = $row;
+    }
+
+    ksort($rows);
+
+    $output['#attached']['library'][] = 'system/drupal.system.modules';
+
+    $output['filters'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['table-filter', 'js-show'],
+      ],
+    ];
+    $output['filters']['text'] = [
+      '#type' => 'search',
+      '#title' => $this->t('Search'),
+      '#size' => 30,
+      '#placeholder' => $this->t('Enter entity type id, provider or class'),
+      '#attributes' => [
+        'class' => ['table-filter-text'],
+        'data-table' => '.devel-filter-text',
+        'autocomplete' => 'off',
+        'title' => $this->t('Enter a part of the entity type id, provider or class to filter by.'),
+      ],
+    ];
+    $output['entities'] = [
+      '#type' => 'table',
+      '#header' => $headers,
+      '#rows' => $rows,
+      '#empty' => $this->t('No entity types found.'),
+      '#sticky' => TRUE,
+      '#attributes' => [
+        'class' => ['devel-entity-type-list', 'devel-filter-text'],
+      ],
+    ];
+
+    return $output;
+  }
+
+  /**
+   * Returns a render array representation of the entity type.
+   *
+   * @param string $entity_type_id
+   *   The name of the entity type to retrieve.
+   *
+   * @return array
+   *   A render array containing the entity type.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   *   If the requested entity type is not defined.
+   */
+  public function entityTypeDetail($entity_type_id) {
+    if (!$entity_type = $this->entityTypeManager()->getDefinition($entity_type_id, FALSE)) {
+      throw new NotFoundHttpException();
+    }
+
+    return $this->dumper->exportAsRenderable($entity_type, $entity_type_id);
+  }
+
+}
diff --git a/web/modules/devel/src/Controller/LayoutInfoController.php b/web/modules/devel/src/Controller/LayoutInfoController.php
new file mode 100644
index 0000000000..4a2cff2c1e
--- /dev/null
+++ b/web/modules/devel/src/Controller/LayoutInfoController.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\devel\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Layout\LayoutPluginManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns response for Layout Info route.
+ */
+class LayoutInfoController extends ControllerBase {
+
+  /**
+   * The Layout Plugin Manager.
+   *
+   * @var Drupal\Core\Layout\LayoutPluginManagerInterface
+   */
+  protected $layoutPluginManager;
+
+  /**
+   * LayoutInfoController constructor.
+   *
+   * @param \Drupal\Core\Layout\LayoutPluginManagerInterface $pluginManagerLayout
+   *   The layout manager.
+   */
+  public function __construct(LayoutPluginManagerInterface $pluginManagerLayout) {
+    $this->layoutPluginManager = $pluginManagerLayout;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.core.layout')
+    );
+  }
+
+  /**
+   * Builds the Layout Info page.
+   *
+   * @return array
+   *   Array of page elements to render.
+   */
+  public function layoutInfoPage() {
+    $definedLayouts = [];
+    $layouts = $this->layoutPluginManager->getDefinitions();
+    foreach ($layouts as $layout) {
+      // @todo Revisit once https://www.drupal.org/node/2660124 gets in, getting
+      // the image should be as simple as $layout->getIcon().
+      $image = NULL;
+      if ($layout->getIconPath() != NULL) {
+        $image = [
+          'data' => [
+            '#theme' => 'image',
+            '#uri' => $layout->getIconPath(),
+            '#alt' => $layout->getLabel(),
+            '#height' => '65',
+          ]
+        ];
+      }
+      $definedLayouts[] = [
+        $image,
+        $layout->getLabel(),
+        $layout->getDescription(),
+        $layout->getCategory(),
+        implode(', ', $layout->getRegionLabels()),
+        $layout->getProvider(),
+      ];
+    }
+
+    return [
+      '#theme' => 'table',
+      '#header' => [
+        $this->t('Icon'),
+        $this->t('Label'),
+        $this->t('Description'),
+        $this->t('Category'),
+        $this->t('Regions'),
+        $this->t('Provider'),
+      ],
+      '#rows' => $definedLayouts,
+      '#empty' => $this->t('No layouts available.'),
+      '#attributes' => [
+        'class' => ['devel-layout-list'],
+      ],
+    ];
+  }
+
+}
diff --git a/web/modules/devel/src/DevelDumperBase.php b/web/modules/devel/src/DevelDumperBase.php
index 41713e9b13..5cfe70dd4e 100644
--- a/web/modules/devel/src/DevelDumperBase.php
+++ b/web/modules/devel/src/DevelDumperBase.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\devel;
 
-use Drupal\Core\Render\Markup;
 use Drupal\Core\Plugin\PluginBase;
+use Drupal\devel\Render\FilteredMarkup;
 
 /**
  * Defines a base devel dumper implementation.
@@ -39,7 +39,7 @@ public function exportAsRenderable($input, $name = NULL) {
    *   The unaltered input value.
    */
   protected function setSafeMarkup($input) {
-    return Markup::create($input);
+    return FilteredMarkup::create($input);
   }
 
 }
diff --git a/web/modules/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php b/web/modules/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php
index a0be6f1715..596e709b4a 100644
--- a/web/modules/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php
+++ b/web/modules/devel/src/EventSubscriber/ThemeInfoRebuildSubscriber.php
@@ -75,6 +75,10 @@ public function rebuildThemeInfo(GetResponseEvent $event) {
       drupal_theme_rebuild();
       // Refresh theme data.
       $this->themeHandler->refreshInfo();
+      // Resets the internal state of the theme handler and clear the 'system
+      // list' cache; this allow to properly register, if needed, PSR-4
+      // namespaces for theme extensions after refreshing the info data.
+      $this->themeHandler->reset();
       // Notify the user that the theme info are rebuilt on every request.
       $this->triggerWarningIfNeeded($event->getRequest());
     }
diff --git a/web/modules/devel/src/Form/ConfigEditor.php b/web/modules/devel/src/Form/ConfigEditor.php
index f8c9858672..e4fe88cc2f 100644
--- a/web/modules/devel/src/Form/ConfigEditor.php
+++ b/web/modules/devel/src/Form/ConfigEditor.php
@@ -32,7 +32,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $config_n
       return;
     }
 
-    $data = $config->get();
+    $data = $config->getOriginal();
 
     if (empty($data)) {
       drupal_set_message(t('Config @name exists but has no data.', array('@name' => $config_name)), 'warning');
diff --git a/web/modules/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php b/web/modules/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php
index 54e8aaea43..9df1aec5cf 100644
--- a/web/modules/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php
+++ b/web/modules/devel/src/Plugin/Devel/Dumper/DoctrineDebug.php
@@ -3,6 +3,7 @@
 namespace Drupal\devel\Plugin\Devel\Dumper;
 
 use Doctrine\Common\Util\Debug;
+use Drupal\Component\Utility\Xss;
 use Drupal\devel\DevelDumperBase;
 
 /**
@@ -28,6 +29,10 @@ public function export($input, $name = NULL) {
     $dump = ob_get_contents();
     ob_end_clean();
 
+    // Run Xss::filterAdmin on the resulting string to prevent
+    // cross-site-scripting (XSS) vulnerabilities.
+    $dump = Xss::filterAdmin($dump);
+
     $dump = '<pre>' . $name . $dump . '</pre>';
 
     return $this->setSafeMarkup($dump);
diff --git a/web/modules/devel/src/Plugin/Devel/Dumper/DrupalVariable.php b/web/modules/devel/src/Plugin/Devel/Dumper/DrupalVariable.php
index 3f38d15f25..a1561e3e96 100644
--- a/web/modules/devel/src/Plugin/Devel/Dumper/DrupalVariable.php
+++ b/web/modules/devel/src/Plugin/Devel/Dumper/DrupalVariable.php
@@ -3,6 +3,7 @@
 namespace Drupal\devel\Plugin\Devel\Dumper;
 
 use Drupal\Component\Utility\Variable;
+use Drupal\Component\Utility\Xss;
 use Drupal\devel\DevelDumperBase;
 
 /**
@@ -21,7 +22,11 @@ class DrupalVariable extends DevelDumperBase {
    */
   public function export($input, $name = NULL) {
     $name = $name ? $name . ' => ' : '';
-    $dump = '<pre>' . $name . Variable::export($input) . '</pre>';
+    $dump = Variable::export($input);
+    // Run Xss::filterAdmin on the resulting string to prevent
+    // cross-site-scripting (XSS) vulnerabilities.
+    $dump = Xss::filterAdmin($dump);
+    $dump = '<pre>' . $name . $dump . '</pre>';
     return $this->setSafeMarkup($dump);
   }
 
diff --git a/web/modules/devel/src/Render/FilteredMarkup.php b/web/modules/devel/src/Render/FilteredMarkup.php
new file mode 100644
index 0000000000..d43c0d5d8a
--- /dev/null
+++ b/web/modules/devel/src/Render/FilteredMarkup.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\devel\Render;
+
+use Drupal\Component\Render\MarkupInterface;
+use Drupal\Component\Render\MarkupTrait;
+
+/**
+ * Defines an object that passes safe strings through the Devel system.
+ *
+ * This object should only be constructed with a known safe string. If there is
+ * any risk that the string contains user-entered data that has not been
+ * filtered first, it must not be used.
+ *
+ * @internal
+ *   This object is marked as internal because it should only be used in the
+ *   Devel module.
+ * @see \Drupal\Core\Render\Markup
+ */
+final class FilteredMarkup implements MarkupInterface, \Countable {
+  use MarkupTrait;
+
+}
\ No newline at end of file
diff --git a/web/modules/devel/src/ToolbarHandler.php b/web/modules/devel/src/ToolbarHandler.php
index 0c890de38f..0d7cbcdf0a 100644
--- a/web/modules/devel/src/ToolbarHandler.php
+++ b/web/modules/devel/src/ToolbarHandler.php
@@ -61,7 +61,7 @@ public function __construct(MenuLinkTreeInterface $menu_link_tree, ConfigFactory
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('menu.link_tree'),
+      $container->get('toolbar.menu_tree'),
       $container->get('config.factory'),
       $container->get('current_user')
     );
diff --git a/web/modules/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml b/web/modules/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml
index b7a5c43e96..265aeb347f 100644
--- a/web/modules/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml
+++ b/web/modules/devel/tests/modules/devel_dumper_test/devel_dumper_test.info.yml
@@ -5,8 +5,8 @@ package: Testing
 # version: VERSION
 # core: 8.x
 
-# Information added by Drupal.org packaging script on 2017-04-23
-version: '8.x-1.0-rc2'
+# Information added by Drupal.org packaging script on 2017-10-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'devel'
-datestamp: 1492989248
+datestamp: 1507197848
diff --git a/web/modules/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml b/web/modules/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml
index 4f5e5ce2ca..4b01554cfb 100644
--- a/web/modules/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml
+++ b/web/modules/devel/tests/modules/devel_entity_test/devel_entity_test.info.yml
@@ -9,8 +9,8 @@ dependencies:
   - text
   - entity_test
 
-# Information added by Drupal.org packaging script on 2017-04-23
-version: '8.x-1.0-rc2'
+# Information added by Drupal.org packaging script on 2017-10-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'devel'
-datestamp: 1492989248
+datestamp: 1507197848
diff --git a/web/modules/devel/tests/modules/devel_test/devel_test.info.yml b/web/modules/devel/tests/modules/devel_test/devel_test.info.yml
index 78000b513b..191c825557 100644
--- a/web/modules/devel/tests/modules/devel_test/devel_test.info.yml
+++ b/web/modules/devel/tests/modules/devel_test/devel_test.info.yml
@@ -5,8 +5,8 @@ package: Testing
 # version: VERSION
 # core: 8.x
 
-# Information added by Drupal.org packaging script on 2017-04-23
-version: '8.x-1.0-rc2'
+# Information added by Drupal.org packaging script on 2017-10-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'devel'
-datestamp: 1492989248
+datestamp: 1507197848
diff --git a/web/modules/devel/src/Tests/DevelControllerTest.php b/web/modules/devel/tests/src/Functional/DevelControllerTest.php
similarity index 89%
rename from web/modules/devel/src/Tests/DevelControllerTest.php
rename to web/modules/devel/tests/src/Functional/DevelControllerTest.php
index 770c2b98d0..0fc375afbd 100644
--- a/web/modules/devel/src/Tests/DevelControllerTest.php
+++ b/web/modules/devel/tests/src/Functional/DevelControllerTest.php
@@ -1,22 +1,22 @@
 <?php
 
-namespace Drupal\devel\Tests;
+namespace Drupal\Tests\devel\Functional;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests Devel controller.
  *
  * @group devel
  */
-class DevelControllerTest extends WebTestBase {
+class DevelControllerTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
    *
    * @var array
    */
-  public static $modules = array('devel', 'node', 'entity_test', 'devel_entity_test', 'block');
+  public static $modules = ['devel', 'node', 'entity_test', 'devel_entity_test', 'block'];
 
   /**
    * {@inheritdoc}
@@ -26,35 +26,35 @@ protected function setUp() {
 
     // Create a test entity.
     $random_label = $this->randomMachineName();
-    $data = array('type' => 'entity_test', 'name' => $random_label);
+    $data = ['type' => 'entity_test', 'name' => $random_label];
     $this->entity = entity_create('entity_test', $data);
     $this->entity->save();
 
     // Create a test entity with only canonical route.
     $random_label = $this->randomMachineName();
-    $data = array('type' => 'devel_entity_test_canonical', 'name' => $random_label);
+    $data = ['type' => 'devel_entity_test_canonical', 'name' => $random_label];
     $this->entity_canonical = entity_create('devel_entity_test_canonical', $data);
     $this->entity_canonical->save();
 
     // Create a test entity with only edit route.
     $random_label = $this->randomMachineName();
-    $data = array('type' => 'devel_entity_test_edit', 'name' => $random_label);
+    $data = ['type' => 'devel_entity_test_edit', 'name' => $random_label];
     $this->entity_edit = entity_create('devel_entity_test_edit', $data);
     $this->entity_edit->save();
 
     // Create a test entity with no routes.
     $random_label = $this->randomMachineName();
-    $data = array('type' => 'devel_entity_test_no_links', 'name' => $random_label);
+    $data = ['type' => 'devel_entity_test_no_links', 'name' => $random_label];
     $this->entity_no_links = entity_create('devel_entity_test_no_links', $data);
     $this->entity_no_links->save();
 
     $this->drupalPlaceBlock('local_tasks_block');
 
-    $web_user = $this->drupalCreateUser(array(
+    $web_user = $this->drupalCreateUser([
       'view test entity',
       'administer entity_test content',
       'access devel information',
-    ));
+    ]);
     $this->drupalLogin($web_user);
   }
 
diff --git a/web/modules/devel/src/Tests/DevelDumperTest.php b/web/modules/devel/tests/src/Functional/DevelDumperTest.php
similarity index 69%
rename from web/modules/devel/src/Tests/DevelDumperTest.php
rename to web/modules/devel/tests/src/Functional/DevelDumperTest.php
index bf214a0ca9..a4ef9b48fc 100644
--- a/web/modules/devel/src/Tests/DevelDumperTest.php
+++ b/web/modules/devel/tests/src/Functional/DevelDumperTest.php
@@ -1,16 +1,16 @@
 <?php
 
-namespace Drupal\devel\Tests;
+namespace Drupal\Tests\devel\Functional;
 
 use Drupal\Component\Render\FormattableMarkup;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests pluggable dumper feature.
  *
  * @group devel
  */
-class DevelDumperTest extends WebTestBase {
+class DevelDumperTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -36,36 +36,42 @@ public function testDumpersConfiguration() {
     $this->drupalGet('admin/config/development/devel');
 
     // Ensures that the dumper input is present on the config page.
-    $this->assertFieldByName('dumper');
+    $this->assertSession()->fieldExists('dumper');
 
     // Ensures that the 'default' dumper is enabled by default.
-    $this->assertFieldChecked('edit-dumper-default');
+    $this->assertSession()->checkboxChecked('edit-dumper-default');
 
     // Ensures that all dumpers declared by devel are present on the config page
     // and that only the available dumpers are selectable.
-    $dumpers = ['default', 'drupal_variable', 'firephp', 'chromephp', 'var_dumper'];
+    $dumpers = [
+      'default',
+      'drupal_variable',
+      'firephp',
+      'chromephp',
+      'var_dumper',
+    ];
     $available_dumpers = ['default', 'drupal_variable'];
 
     foreach ($dumpers as $dumper) {
-      $this->assertFieldByXPath('//input[@type="radio" and @name="dumper"]', $dumper, new FormattableMarkup('Radio button for @dumper found.', ['@dumper' => $dumper]));
+      $this->assertFieldByXPath('//input[@type="radio" and @name="dumper"]', $dumper);
       if (in_array($dumper, $available_dumpers)) {
-        $this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', $dumper, new FormattableMarkup('Dumper @dumper is available.', ['@dumper' => $dumper]));
+        $this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', $dumper);
       }
       else {
-        $this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', $dumper, new FormattableMarkup('Dumper @dumper is disabled.', ['@dumper' => $dumper]));
+        $this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', $dumper);
       }
     }
 
     // Ensures that dumper plugins declared by other modules are present on the
     // config page and that only the available dumpers are selectable.
-    $this->assertFieldByName('dumper', 'available_test_dumper');
-    $this->assertText('Available test dumper.', 'Available dumper label is present');
-    $this->assertText('Drupal dumper for testing purposes (available).', 'Available dumper description is present');
+    $this->assertFieldByXPath('//input[@name="dumper"]', 'available_test_dumper');
+    $this->assertSession()->pageTextContains('Available test dumper.');
+    $this->assertSession()->pageTextContains('Drupal dumper for testing purposes (available).');
     $this->assertFieldByXPath('//input[@name="dumper" and not(@disabled="disabled")]', 'available_test_dumper', 'Available dumper input not is disabled.');
 
-    $this->assertFieldByName('dumper', 'not_available_test_dumper');
-    $this->assertText('Not available test dumper.', 'Non available dumper label is present');
-    $this->assertText('Drupal dumper for testing purposes (not available).Not available. You may need to install external dependencies for use this plugin.', 'Non available dumper description is present');
+    $this->assertFieldByXPath('//input[@name="dumper"]', 'not_available_test_dumper');
+    $this->assertSession()->pageTextContains('Not available test dumper.');
+    $this->assertSession()->pageTextContains('Drupal dumper for testing purposes (not available).Not available. You may need to install external dependencies for use this plugin.');
     $this->assertFieldByXPath('//input[@name="dumper" and @disabled="disabled"]', 'not_available_test_dumper', 'Non available dumper input is disabled.');
 
     // Ensures that saving of the dumpers configuration works as expected.
@@ -73,10 +79,10 @@ public function testDumpersConfiguration() {
       'dumper' => 'drupal_variable',
     ];
     $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
-    $this->assertText(t('The configuration options have been saved.'));
+    $this->assertSession()->pageTextContains(t('The configuration options have been saved.'));
 
     $config = \Drupal::config('devel.settings')->get('devel_dumper');
-    $this->assertEqual('drupal_variable', $config, 'The configuration options have been properly saved');
+    $this->assertEquals('drupal_variable', $config, 'The configuration options have been properly saved');
 
     // Ensure that if the chosen dumper is not available (e.g. the module that
     // provide it is uninstalled) the 'default' dumper appears selected in the
@@ -84,22 +90,22 @@ public function testDumpersConfiguration() {
     \Drupal::service('module_installer')->install(['kint']);
 
     $this->drupalGet('admin/config/development/devel');
-    $this->assertFieldByName('dumper', 'kint');
+    $this->assertFieldByXPath('//input[@name="dumper"]', 'kint');
 
     $edit = [
       'dumper' => 'kint',
     ];
     $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
-    $this->assertText(t('The configuration options have been saved.'));
+    $this->assertSession()->pageTextContains(t('The configuration options have been saved.'));
 
     $config = \Drupal::config('devel.settings')->get('devel_dumper');
-    $this->assertEqual('kint', $config, 'The configuration options have been properly saved');
+    $this->assertEquals('kint', $config, 'The configuration options have been properly saved');
 
     \Drupal::service('module_installer')->uninstall(['kint']);
 
     $this->drupalGet('admin/config/development/devel');
-    $this->assertNoFieldByName('dumper', 'kint');
-    $this->assertFieldChecked('edit-dumper-default');
+    $this->assertNoFieldByXPath('//input[@name="dumper"]', 'kint');
+    $this->assertSession()->checkboxChecked('edit-dumper-default');
   }
 
   /**
@@ -110,7 +116,7 @@ function testDumpersOutput() {
       'dumper' => 'available_test_dumper',
     ];
     $this->drupalPostForm('admin/config/development/devel', $edit, t('Save configuration'));
-    $this->assertText(t('The configuration options have been saved.'));
+    $this->assertSession()->pageTextContains(t('The configuration options have been saved.'));
 
     $this->drupalGet('devel_dumper_test/dump');
     $elements = $this->xpath('//body/pre[contains(text(), :message)]', [':message' => 'AvailableTestDumper::dump() Test output']);
@@ -129,8 +135,8 @@ function testDumpersOutput() {
     $this->assertTrue(!empty($elements), 'Dumped message is present.');
     // Ensures that plugins can add libraries to the page when the
     // ::exportAsRenderable() method is used.
-    $this->assertRaw('devel_dumper_test/css/devel_dumper_test.css');
-    $this->assertRaw('devel_dumper_test/js/devel_dumper_test.js');
+    $this->assertSession()->responseContains('devel_dumper_test/css/devel_dumper_test.css');
+    $this->assertSession()->responseContains('devel_dumper_test/js/devel_dumper_test.js');
 
     $debug_filename = file_directory_temp() . '/drupal_debug.txt';
 
@@ -140,7 +146,7 @@ function testDumpersOutput() {
 <pre>AvailableTestDumper::export() Test output</pre>
 
 EOF;
-    $this->assertEqual($file_content, $expected, 'Dumped message is present.');
+    $this->assertEquals($file_content, $expected, 'Dumped message is present.');
 
     // Ensures that the DevelDumperManager::debug() is not access checked and
     // that the dump is written in the debug file even if the user has not the
@@ -153,7 +159,7 @@ function testDumpersOutput() {
 <pre>AvailableTestDumper::export() Test output</pre>
 
 EOF;
-    $this->assertEqual($file_content, $expected, 'Dumped message is present.');
+    $this->assertEquals($file_content, $expected, 'Dumped message is present.');
   }
 
 }
diff --git a/web/modules/devel/tests/src/Functional/DevelElementInfoTest.php b/web/modules/devel/tests/src/Functional/DevelElementInfoTest.php
new file mode 100644
index 0000000000..2698e24da3
--- /dev/null
+++ b/web/modules/devel/tests/src/Functional/DevelElementInfoTest.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\Tests\devel\Functional;
+
+use Behat\Mink\Element\NodeElement;
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests element info pages and links.
+ *
+ * @group devel
+ */
+class DevelElementInfoTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['devel', 'block'];
+
+  /**
+   * The user for the test.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $develUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalPlaceBlock('system_menu_block:devel');
+    $this->drupalPlaceBlock('page_title_block');
+
+    $this->develUser = $this->drupalCreateUser(['access devel information']);
+    $this->drupalLogin($this->develUser);
+  }
+
+  /**
+   * Tests element info menu link.
+   */
+  public function testElementInfoMenuLink() {
+    $this->drupalPlaceBlock('system_menu_block:devel');
+    // Ensures that the element info link is present on the devel menu and that
+    // it points to the correct page.
+    $this->drupalGet('');
+    $this->clickLink('Element Info');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->addressEquals('/devel/elements');
+    $this->assertSession()->pageTextContains('Element Info');
+  }
+
+  /**
+   * Tests element list page.
+   */
+  public function testElementList() {
+    $this->drupalGet('/devel/elements');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Element Info');
+
+    $page = $this->getSession()->getPage();
+
+    // Ensures that the element list table is found.
+    $table = $page->find('css', 'table.devel-element-list');
+    $this->assertNotNull($table);
+
+    // Ensures that the expected table headers are found.
+    $headers = $table->findAll('css', 'thead th');
+    $this->assertEquals(4, count($headers));
+
+    $expected_headers = ['Name', 'Provider', 'Class', 'Operations'];
+    $actual_headers = array_map(function (NodeElement $element) {
+      return $element->getText();
+    }, $headers);
+    $this->assertSame($expected_headers, $actual_headers);
+
+    // Tests the presence of some (arbitrarily chosen) elements in the table.
+    $expected_elements = [
+      'button' => [
+        'class' => 'Drupal\Core\Render\Element\Button',
+        'provider' => 'core',
+      ],
+      'form' => [
+        'class' => 'Drupal\Core\Render\Element\Form',
+        'provider' => 'core',
+      ],
+      'html' => [
+        'class' => 'Drupal\Core\Render\Element\Html',
+        'provider' => 'core',
+      ],
+    ];
+
+    foreach ($expected_elements as $element_name => $element) {
+      $row = $table->find('css', sprintf('tbody tr:contains("%s")', $element_name));
+      $this->assertNotNull($row);
+
+      /** @var $cells \Behat\Mink\Element\NodeElement[] */
+      $cells = $row->findAll('css', 'td');
+      $this->assertEquals(4, count($cells));
+
+      $cell = $cells[0];
+      $this->assertEquals($element_name, $cell->getText());
+      $this->assertTrue($cell->hasClass('table-filter-text-source'));
+
+      $cell = $cells[1];
+      $this->assertEquals($element['provider'], $cell->getText());
+      $this->assertTrue($cell->hasClass('table-filter-text-source'));
+
+      $cell = $cells[2];
+      $this->assertEquals($element['class'], $cell->getText());
+      $this->assertTrue($cell->hasClass('table-filter-text-source'));
+
+      $cell = $cells[3];
+      $actual_href = $cell->findLink('Devel')->getAttribute('href');
+      $expected_href = Url::fromRoute('devel.elements_page.detail', ['element_name' => $element_name])->toString();
+      $this->assertEquals($expected_href, $actual_href);
+    }
+
+    // Ensures that the page is accessible only to the users with the adequate
+    // permissions.
+    $this->drupalLogout();
+    $this->drupalGet('devel/elements');
+    $this->assertSession()->statusCodeEquals(403);
+  }
+
+  /**
+   * Tests element detail page.
+   */
+  public function testElementDetail() {
+    $element_name = 'button';
+
+    // Ensures that the page works as expected.
+    $this->drupalGet("/devel/elements/$element_name");
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains("Element $element_name");
+
+    // Ensures that the page returns a 404 error if the requested element is
+    // not defined.
+    $this->drupalGet('/devel/elements/not_exists');
+    $this->assertSession()->statusCodeEquals(404);
+
+    // Ensures that the page is accessible ony to users with the adequate
+    // permissions.
+    $this->drupalLogout();
+    $this->drupalGet("/devel/elements/$element_name");
+    $this->assertSession()->statusCodeEquals(403);
+  }
+
+}
diff --git a/web/modules/devel/tests/src/Functional/DevelEntityTypeInfoTest.php b/web/modules/devel/tests/src/Functional/DevelEntityTypeInfoTest.php
new file mode 100644
index 0000000000..3a708e5155
--- /dev/null
+++ b/web/modules/devel/tests/src/Functional/DevelEntityTypeInfoTest.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace Drupal\Tests\devel\Functional;
+
+use Behat\Mink\Element\NodeElement;
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests entity type info pages and links.
+ *
+ * @group devel
+ */
+class DevelEntityTypeInfoTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['devel', 'block'];
+
+  /**
+   * The user for the test.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $develUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalPlaceBlock('system_menu_block:devel');
+    $this->drupalPlaceBlock('page_title_block');
+
+    $this->develUser = $this->drupalCreateUser(['access devel information']);
+    $this->drupalLogin($this->develUser);
+  }
+
+  /**
+   * Tests entity info menu link.
+   */
+  public function testEntityInfoMenuLink() {
+    $this->drupalPlaceBlock('system_menu_block:devel');
+    // Ensures that the entity type info link is present on the devel menu and that
+    // it points to the correct page.
+    $this->drupalGet('');
+    $this->clickLink('Entity Info');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->addressEquals('/devel/entity/info');
+    $this->assertSession()->pageTextContains('Entity Info');
+  }
+
+  /**
+   * Tests entity type list page.
+   */
+  public function testEntityTypeList() {
+    $this->drupalGet('/devel/entity/info');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Entity Info');
+
+    $page = $this->getSession()->getPage();
+
+    // Ensures that the entity type list table is found.
+    $table = $page->find('css', 'table.devel-entity-type-list');
+    $this->assertNotNull($table);
+
+    // Ensures that the expected table headers are found.
+    $headers = $table->findAll('css', 'thead th');
+    $this->assertEquals(5, count($headers));
+
+    $expected_headers = ['ID', 'Name', 'Provider', 'Class', 'Operations'];
+    $actual_headers = array_map(function (NodeElement $element) {
+      return $element->getText();
+    }, $headers);
+    $this->assertSame($expected_headers, $actual_headers);
+
+    // Tests the presence of some (arbitrarily chosen) entity types in the table.
+    $expected_types = [
+      'date_format' => [
+        'name' => 'Date format',
+        'class' => 'Drupal\Core\Datetime\Entity\DateFormat',
+        'provider' => 'core',
+      ],
+      'block' => [
+        'name' => 'Block',
+        'class' => 'Drupal\block\Entity\Block',
+        'provider' => 'block',
+      ],
+      'entity_view_mode' => [
+        'name' => 'View mode',
+        'class' => 'Drupal\Core\Entity\Entity\EntityViewMode',
+        'provider' => 'core',
+      ],
+    ];
+
+    foreach ($expected_types as $entity_type_id => $entity_type) {
+      $row = $table->find('css', sprintf('tbody tr:contains("%s")', $entity_type_id));
+      $this->assertNotNull($row);
+
+      /** @var $cells \Behat\Mink\Element\NodeElement[] */
+      $cells = $row->findAll('css', 'td');
+      $this->assertEquals(5, count($cells));
+
+      $cell = $cells[0];
+      $this->assertEquals($entity_type_id, $cell->getText());
+      $this->assertTrue($cell->hasClass('table-filter-text-source'));
+
+      $cell = $cells[1];
+      $this->assertEquals($entity_type['name'], $cell->getText());
+      $this->assertTrue($cell->hasClass('table-filter-text-source'));
+
+      $cell = $cells[2];
+      $this->assertEquals($entity_type['provider'], $cell->getText());
+      $this->assertTrue($cell->hasClass('table-filter-text-source'));
+
+      $cell = $cells[3];
+      $this->assertEquals($entity_type['class'], $cell->getText());
+      $this->assertTrue($cell->hasClass('table-filter-text-source'));
+
+      $cell = $cells[4];
+      $actual_href = $cell->findLink('Devel')->getAttribute('href');
+      $expected_href = Url::fromRoute('devel.entity_info_page.detail', ['entity_type_id' => $entity_type_id])->toString();
+      $this->assertEquals($expected_href, $actual_href);
+    }
+
+    // Ensures that the page is accessible only to the users with the adequate
+    // permissions.
+    $this->drupalLogout();
+    $this->drupalGet('devel/entity/info');
+    $this->assertSession()->statusCodeEquals(403);
+  }
+
+  /**
+   * Tests entity type detail page.
+   */
+  public function testEntityTypeDetail() {
+    $entity_type_id = 'date_format';
+
+    // Ensures that the page works as expected.
+    $this->drupalGet("/devel/entity/info/$entity_type_id");
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains("Entity type $entity_type_id");
+
+    // Ensures that the page returns a 404 error if the requested entity type is
+    // not defined.
+    $this->drupalGet('/devel/entity/info/not_exists');
+    $this->assertSession()->statusCodeEquals(404);
+
+    // Ensures that the page is accessible ony to users with the adequate
+    // permissions.
+    $this->drupalLogout();
+    $this->drupalGet("/devel/entity/info/$entity_type_id");
+    $this->assertSession()->statusCodeEquals(403);
+  }
+
+}
diff --git a/web/modules/devel/tests/src/Functional/DevelLayoutInfoTest.php b/web/modules/devel/tests/src/Functional/DevelLayoutInfoTest.php
new file mode 100644
index 0000000000..6277de1927
--- /dev/null
+++ b/web/modules/devel/tests/src/Functional/DevelLayoutInfoTest.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\Tests\devel\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests layout info pages and links.
+ *
+ * @group devel
+ */
+class DevelLayoutInfoTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['devel', 'block', 'layout_discovery'];
+
+  /**
+   * The user for the test.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $develUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    // TODO find a cleaner way to skip layout info tests when running tests on
+    // Drupal branch < 8.3.x.
+    if (version_compare(\Drupal::VERSION, '8.3', '<')) {
+      $this->markTestSkipped('Devel Layout Info Tests only available on version 8.3.x+.');
+    }
+
+    parent::setUp();
+
+    $this->drupalPlaceBlock('page_title_block');
+
+    $this->develUser = $this->drupalCreateUser(['access devel information']);
+    $this->drupalLogin($this->develUser);
+  }
+
+  /**
+   * Tests layout info menu link.
+   */
+  public function testLayoutsInfoMenuLink() {
+    $this->drupalPlaceBlock('system_menu_block:devel');
+    // Ensures that the layout info link is present on the devel menu and that
+    // it points to the correct page.
+    $this->drupalGet('');
+    $this->clickLink('Layouts Info');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->addressEquals('/devel/layouts');
+    $this->assertSession()->pageTextContains('Layout');
+  }
+
+  /**
+   * Tests layout info page.
+   */
+  public function testLayoutList() {
+    $this->drupalGet('/devel/layouts');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Layouts');
+
+    $page = $this->getSession()->getPage();
+
+    // Ensures that the layout table is found.
+    $table = $page->find('css', 'table.devel-layout-list');
+    $this->assertNotNull($table);
+
+    // Ensures that the expected table headers are found.
+    /** @var $headers \Behat\Mink\Element\NodeElement[] */
+    $headers = $table->findAll('css', 'thead th');
+    $this->assertEquals(6, count($headers));
+
+    $expected_headers = ['Icon', 'Label', 'Description', 'Category', 'Regions', 'Provider'];
+    $actual_headers = array_map(function ($element) {
+      return $element->getText();
+    }, $headers);
+    $this->assertSame($expected_headers, $actual_headers);
+
+    // Ensures that all the layouts are listed in the table.
+    $layout_manager = \Drupal::service('plugin.manager.core.layout');
+    $layouts = $layout_manager->getDefinitions();
+    $table_rows = $table->findAll('css', 'tbody tr');
+    $this->assertEquals(count($layouts), count($table_rows));
+
+    $index = 0;
+    foreach ($layouts as $layout) {
+      $cells = $table_rows[$index]->findAll('css', 'td');
+      $this->assertEquals(6, count($cells));
+
+      $cell_layout_icon = $cells[0];
+      if (empty($layout->getIconPath())) {
+        // @todo test that the icon path image is set correctly
+      }
+      else {
+        $this->assertNull($cell_layout_icon->getText());
+      }
+
+      $cell_layout_label = $cells[1];
+      $this->assertEquals($cell_layout_label->getText(), $layout->getLabel());
+
+      $cell_layout_description = $cells[2];
+      $this->assertEquals($cell_layout_description->getText(), $layout->getDescription());
+
+      $cell_layout_category = $cells[3];
+      $this->assertEquals($cell_layout_category->getText(), $layout->getCategory());
+
+      $cell_layout_regions = $cells[4];
+      $this->assertEquals($cell_layout_regions->getText(), implode(', ', $layout->getRegionLabels()));
+
+      $cell_layout_provider = $cells[5];
+      $this->assertEquals($cell_layout_provider->getText(), $layout->getProvider());
+
+      $index++;
+    }
+
+    // Ensures that the page is accessible only to the users with the adequate
+    // permissions.
+    $this->drupalLogout();
+    $this->drupalGet('devel/layouts');
+    $this->assertSession()->statusCodeEquals(403);
+  }
+
+  /**
+   * Tests the dependency with layout_discovery module.
+   */
+  public function testLayoutDiscoveryDependency() {
+    $this->container->get('module_installer')->uninstall(['layout_discovery']);
+    $this->drupalPlaceBlock('system_menu_block:devel');
+
+    // Ensures that the layout info link is not present on the devel menu.
+    $this->drupalGet('');
+    $this->assertSession()->linkNotExists('Layouts Info');
+
+    // Ensures that the layouts info page is not available.
+    $this->drupalGet('/devel/layouts');
+    $this->assertSession()->statusCodeEquals(404);
+
+    // Check a few other devel pages to verify devel module stil works.
+    $this->drupalGet('/devel/events');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->drupalGet('devel/routes');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->drupalGet('/devel/container/service');
+    $this->assertSession()->statusCodeEquals(200);
+  }
+
+}
diff --git a/web/modules/devel/src/Tests/DevelMenuLinksTest.php b/web/modules/devel/tests/src/Functional/DevelMenuLinksTest.php
similarity index 95%
rename from web/modules/devel/src/Tests/DevelMenuLinksTest.php
rename to web/modules/devel/tests/src/Functional/DevelMenuLinksTest.php
index 1183745f70..e806453add 100644
--- a/web/modules/devel/src/Tests/DevelMenuLinksTest.php
+++ b/web/modules/devel/tests/src/Functional/DevelMenuLinksTest.php
@@ -1,16 +1,16 @@
 <?php
 
-namespace Drupal\devel\Tests;
+namespace Drupal\Tests\devel\Functional;
 
 use Drupal\Core\Url;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests devel menu links.
  *
  * @group devel
  */
-class DevelMenuLinksTest extends WebTestBase {
+class DevelMenuLinksTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
diff --git a/web/modules/devel/src/Tests/DevelReinstallTest.php b/web/modules/devel/tests/src/Functional/DevelModulesReinstallTest.php
similarity index 67%
rename from web/modules/devel/src/Tests/DevelReinstallTest.php
rename to web/modules/devel/tests/src/Functional/DevelModulesReinstallTest.php
index 9ff5efa03f..5068f2cab5 100644
--- a/web/modules/devel/src/Tests/DevelReinstallTest.php
+++ b/web/modules/devel/tests/src/Functional/DevelModulesReinstallTest.php
@@ -1,22 +1,22 @@
 <?php
 
-namespace Drupal\devel\Tests;
+namespace Drupal\Tests\devel\Functional;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests reinstall modules.
  *
  * @group devel
  */
-class DevelReinstallTest extends WebTestBase {
+class DevelModulesReinstallTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
    *
    * @var array
    */
-  public static $modules = array('devel');
+  public static $modules = ['devel'];
 
   /**
    * The profile to install as a basis for testing.
@@ -31,7 +31,7 @@ class DevelReinstallTest extends WebTestBase {
   protected function setUp() {
     parent::setUp();
 
-    $web_user = $this->drupalCreateUser(array('administer site configuration'));
+    $web_user = $this->drupalCreateUser(['administer site configuration']);
     $this->drupalLogin($web_user);
   }
 
@@ -40,7 +40,7 @@ protected function setUp() {
    */
   public function testDevelReinstallModules() {
     // Minimal profile enables only dblog, block and node.
-    $modules = array('dblog', 'block');
+    $modules = ['dblog', 'block'];
 
     // Needed for compare correctly the message.
     sort($modules);
@@ -48,13 +48,13 @@ public function testDevelReinstallModules() {
     $this->drupalGet('devel/reinstall');
 
     // Prepare field data in an associative array
-    $edit = array();
+    $edit = [];
     foreach ($modules as $module) {
       $edit["reinstall[$module]"] = TRUE;
     }
 
     $this->drupalPostForm('devel/reinstall', $edit, t('Reinstall'));
-    $this->assertText(t('Uninstalled and installed: @names.', array('@names' => implode(', ', $modules))));
+    $this->assertText(t('Uninstalled and installed: @names.', ['@names' => implode(', ', $modules)]));
   }
 
 }
diff --git a/web/modules/devel/tests/src/Functional/DevelRequirementsTest.php b/web/modules/devel/tests/src/Functional/DevelRequirementsTest.php
index faa6bf1f52..876a5ff4f7 100644
--- a/web/modules/devel/tests/src/Functional/DevelRequirementsTest.php
+++ b/web/modules/devel/tests/src/Functional/DevelRequirementsTest.php
@@ -27,7 +27,7 @@ public function testStatusPage() {
     $this->assertSession()->statusCodeEquals(200);
 
     $this->assertSession()->pageTextContains('Devel module enabled');
-    $this->assertSession()->pageTextContains('The module provide the access to debug informations, therefore is recommended to disable the Devel module on production sites.');
+    $this->assertSession()->pageTextContains('The Devel module provides access to internal debugging information; therefore it\'s recommended to disable this module on sites in production.');
   }
 
 }
diff --git a/web/modules/devel/src/Tests/DevelSwitchUserTest.php b/web/modules/devel/tests/src/Functional/DevelSwitchUserTest.php
similarity index 92%
rename from web/modules/devel/src/Tests/DevelSwitchUserTest.php
rename to web/modules/devel/tests/src/Functional/DevelSwitchUserTest.php
index 68f6136db6..a0c89daa94 100644
--- a/web/modules/devel/src/Tests/DevelSwitchUserTest.php
+++ b/web/modules/devel/tests/src/Functional/DevelSwitchUserTest.php
@@ -1,16 +1,16 @@
 <?php
 
-namespace Drupal\devel\Tests;
+namespace Drupal\Tests\devel\Functional;
 
 use Drupal\Component\Render\FormattableMarkup;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests switch user.
  *
  * @group devel
  */
-class DevelSwitchUserTest extends WebTestBase {
+class DevelSwitchUserTest extends BrowserTestBase {
 
   /**
    * Modules to enable.
@@ -70,7 +70,7 @@ public function testSwitchUser() {
     $this->assertNoText($this->block->label(), 'Block title was not found.');
 
     // Ensure that a token is required to switch user.
-    $this->drupalGet('/devel/switch/' . $this->webUser->getUsername());
+    $this->drupalGet('/devel/switch/' . $this->webUser->getDisplayName());
     $this->assertResponse(403);
 
     $this->drupalLogin($this->develUser);
@@ -83,22 +83,22 @@ public function testSwitchUser() {
     $this->assertResponse(403);
 
     // Ensure that a token is required to switch user.
-    $this->drupalGet('/devel/switch/' . $this->switchUser->getUsername());
+    $this->drupalGet('/devel/switch/' . $this->switchUser->getDisplayName());
     $this->assertResponse(403);
 
     // Switch to another user account.
     $this->drupalGet('/user/' . $this->switchUser->id());
-    $this->clickLink($this->switchUser->getUsername());
+    $this->clickLink($this->switchUser->getDisplayName());
     $this->assertSessionByUid($this->switchUser->id());
     $this->assertNoSessionByUid($this->develUser->id());
 
     // Switch back to initial account.
-    $this->clickLink($this->develUser->getUsername());
+    $this->clickLink($this->develUser->getDisplayName());
     $this->assertNoSessionByUid($this->switchUser->id());
     $this->assertSessionByUid($this->develUser->id());
 
     // Use the search form to switch to another account.
-    $edit = ['userid' => $this->switchUser->getUsername()];
+    $edit = ['userid' => $this->switchUser->getDisplayName()];
     $this->drupalPostForm(NULL, $edit, t('Switch'));
     $this->assertSessionByUid($this->switchUser->id());
     $this->assertNoSessionByUid($this->develUser->id());
@@ -166,22 +166,22 @@ public function testSwitchUserListItems() {
 
     // Ensure that user with 'switch users' permission are prioritized.
     $this->assertSwitchUserListCount(2);
-    $this->assertSwitchUserListContainsUser($this->develUser->getUsername());
-    $this->assertSwitchUserListContainsUser($this->switchUser->getUsername());
+    $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName());
+    $this->assertSwitchUserListContainsUser($this->switchUser->getDisplayName());
 
     // Ensure that blocked users are not shown in the list.
     $this->switchUser->set('status', 0)->save();
     $this->drupalGet('');
     $this->assertSwitchUserListCount(2);
-    $this->assertSwitchUserListContainsUser($this->develUser->getUsername());
-    $this->assertSwitchUserListContainsUser($this->webUser->getUsername());
-    $this->assertSwitchUserListNoContainsUser($this->switchUser->getUsername());
+    $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName());
+    $this->assertSwitchUserListContainsUser($this->webUser->getDisplayName());
+    $this->assertSwitchUserListNoContainsUser($this->switchUser->getDisplayName());
 
     // Ensure that anonymous user are prioritized if include_anon is set to true.
     $this->setBlockConfiguration('include_anon', TRUE);
     $this->drupalGet('');
     $this->assertSwitchUserListCount(2);
-    $this->assertSwitchUserListContainsUser($this->develUser->getUsername());
+    $this->assertSwitchUserListContainsUser($this->develUser->getDisplayName());
     $this->assertSwitchUserListContainsUser($anonymous);
 
     // Ensure that the switch user block works properly even if no prioritized
@@ -192,7 +192,7 @@ public function testSwitchUserListItems() {
     $this->drupalLogin($this->rootUser);
     $this->drupalGet('');
     $this->assertSwitchUserListCount(2);
-    $this->assertSwitchUserListContainsUser($this->rootUser->getUsername());
+    $this->assertSwitchUserListContainsUser($this->rootUser->getDisplayName());
     $this->assertSwitchUserListContainsUser($anonymous);
 
     // Ensure that the switch user block works properly even if no roles have
@@ -202,7 +202,7 @@ public function testSwitchUserListItems() {
 
     $this->drupalGet('');
     $this->assertSwitchUserListCount(2);
-    $this->assertSwitchUserListContainsUser($this->rootUser->getUsername());
+    $this->assertSwitchUserListContainsUser($this->rootUser->getDisplayName());
     $this->assertSwitchUserListContainsUser($anonymous);
   }
 
@@ -276,12 +276,12 @@ protected function setBlockConfiguration($key, $value) {
    */
   protected function assertSessionByUid($uid) {
     $query = \Drupal::database()->select('sessions');
-    $query->fields('sessions', array('uid'));
+    $query->fields('sessions', ['uid']);
     $query->condition('uid', $uid);
     $result = $query->execute()->fetchAll();
 
     if (empty($result)) {
-      $this->fail(new FormattableMarkup('No session found for uid @uid', array('@uid' => $uid)));
+      $this->fail(new FormattableMarkup('No session found for uid @uid', ['@uid' => $uid]));
     }
     elseif (count($result) > 1) {
       // If there is more than one session, then that must be unexpected.
@@ -304,7 +304,7 @@ protected function assertSessionByUid($uid) {
    */
   protected function assertNoSessionByUid($uid) {
     $query = \Drupal::database()->select('sessions');
-    $query->fields('sessions', array('uid'));
+    $query->fields('sessions', ['uid']);
     $query->condition('uid', $uid);
     $result = $query->execute()->fetchAll();
     $this->assert(empty($result), "No session for uid $uid found.");
diff --git a/web/modules/devel/tests/src/Functional/DevelToolbarTest.php b/web/modules/devel/tests/src/Functional/DevelToolbarTest.php
index aa2271dbe1..26293f896e 100644
--- a/web/modules/devel/tests/src/Functional/DevelToolbarTest.php
+++ b/web/modules/devel/tests/src/Functional/DevelToolbarTest.php
@@ -182,11 +182,11 @@ public function testToolbarIntegration() {
     $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector);
 
     $devel_menu_items = $this->getMenuLinkInfos();
-    $toolbar_items = $toolbar_tray->findAll('css', 'ul.menu a');
+    $toolbar_items = $toolbar_tray->findAll('css', 'ul.toolbar-menu a');
     $this->assertCount(count($devel_menu_items), $toolbar_items);
 
     foreach ($devel_menu_items as $link) {
-      $item_selector = sprintf('ul.menu a:contains("%s")', $link['title']);
+      $item_selector = sprintf('ul.toolbar-menu a:contains("%s")', $link['title']);
       $item = $this->assertSession()->elementExists('css', $item_selector, $toolbar_tray);
       // TODO: find a more correct way to test link url.
       $this->assertContains(strtok($link['url'], '?'), $item->getAttribute('href'));
@@ -206,7 +206,7 @@ public function testToolbarIntegration() {
 
     $this->drupalGet('');
     $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector);
-    $item = $this->assertSession()->elementExists('css', sprintf('ul.menu a:contains("%s")', 'Events Info'), $toolbar_tray);
+    $item = $this->assertSession()->elementExists('css', sprintf('ul.toolbar-menu a:contains("%s")', 'Events Info'), $toolbar_tray);
     $this->assertFalse($item->hasClass('toolbar-horizontal-item-hidden'));
 
     // Ensures that disabling a menu link it will not more shown in the toolbar
@@ -216,7 +216,7 @@ public function testToolbarIntegration() {
 
     $this->drupalGet('');
     $toolbar_tray = $this->assertSession()->elementExists('css', $toolbar_tray_selector);
-    $this->assertSession()->elementNotExists('css', sprintf('ul.menu a:contains("%s")', 'Events Info'), $toolbar_tray);
+    $this->assertSession()->elementNotExists('css', sprintf('ul.toolbar-menu a:contains("%s")', 'Events Info'), $toolbar_tray);
   }
 
   /**
diff --git a/web/modules/devel/webprofiler/src/Cache/CacheBackendWrapper.php b/web/modules/devel/webprofiler/src/Cache/CacheBackendWrapper.php
index 725e4cf590..4050b41036 100644
--- a/web/modules/devel/webprofiler/src/Cache/CacheBackendWrapper.php
+++ b/web/modules/devel/webprofiler/src/Cache/CacheBackendWrapper.php
@@ -56,7 +56,7 @@ public function get($cid, $allow_invalid = FALSE) {
     $cache = $this->cacheBackend->get($cid, $allow_invalid);
 
     if ($cache) {
-      $cacheCopy = new \StdClass();
+      $cacheCopy = new \stdClass();
       $cacheCopy->cid = $cache->cid;
       $cacheCopy->expire = $cache->expire;
       $cacheCopy->tags = $cache->tags;
@@ -82,7 +82,7 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
         $this->cacheDataCollector->registerCacheMiss($this->bin, $cid);
       }
       else {
-        $cacheCopy = new \StdClass();
+        $cacheCopy = new \stdClass();
         $cacheCopy->cid = $cache[$cid]->cid;
         $cacheCopy->expire = $cache[$cid]->expire;
         $cacheCopy->tags = $cache[$cid]->tags;
diff --git a/web/modules/devel/webprofiler/src/Compiler/EventPass.php b/web/modules/devel/webprofiler/src/Compiler/EventPass.php
deleted file mode 100644
index 5f009afdba..0000000000
--- a/web/modules/devel/webprofiler/src/Compiler/EventPass.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-namespace Drupal\webprofiler\Compiler;
-
-use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Reference;
-
-/**
- * Class EventPass.
- */
-class EventPass implements CompilerPassInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function process(ContainerBuilder $container) {
-    $definition = $container->findDefinition('http_kernel.basic');
-    $definition->replaceArgument(1, new Reference('webprofiler.debug.controller_resolver'));
-
-    // Replace the regular event_dispatcher service with the debug one.
-    $definition = $container->findDefinition('event_dispatcher');
-    $definition->setPublic(FALSE);
-    $container->setDefinition('webprofiler.debug.event_dispatcher.default', $definition);
-    $container->register('event_dispatcher', 'Drupal\webprofiler\TraceableEventDispatcher')
-      ->addArgument(new Reference('webprofiler.debug.event_dispatcher.default'))
-      ->addArgument(new Reference('stopwatch'))
-      ->setProperty('_serviceId', 'event_dispatcher');
-  }
-}
diff --git a/web/modules/devel/webprofiler/src/Compiler/ServicePass.php b/web/modules/devel/webprofiler/src/Compiler/ServicePass.php
index df6b87907f..4928418c63 100644
--- a/web/modules/devel/webprofiler/src/Compiler/ServicePass.php
+++ b/web/modules/devel/webprofiler/src/Compiler/ServicePass.php
@@ -50,7 +50,6 @@ private function extractData(ContainerBuilder $container, ServiceReferenceGraph
           $inEdges[] = [
             'id' => $edge->getSourceNode()->getId(),
             'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL,
-            'strict' => $edgeValue ? $edgeValue->isStrict() : NULL,
           ];
         }
 
@@ -63,7 +62,6 @@ private function extractData(ContainerBuilder $container, ServiceReferenceGraph
           $outEdges[] = [
             'id' => $edge->getDestNode()->getId(),
             'invalidBehavior' => $edgeValue ? $edgeValue->getInvalidBehavior() : NULL,
-            'strict' => $edgeValue ? $edgeValue->isStrict() : NULL,
           ];
         }
       }
diff --git a/web/modules/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php b/web/modules/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php
index bdcf49ceb9..2c766a4150 100644
--- a/web/modules/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php
+++ b/web/modules/devel/webprofiler/src/DataCollector/DatabaseDataCollector.php
@@ -56,7 +56,7 @@ public function collect(Request $request, Response $response, \Exception $except
         unset($query['caller']['args']);
 
         // Remove query args element if empty.
-        if (empty($query['args'])) {
+        if (isset($query['args']) && empty($query['args'])) {
           unset($query['args']);
         }
 
@@ -223,8 +223,11 @@ public function getData() {
       $query['type'] = $type;
 
       $quoted = [];
-      foreach ((array) $query['args'] as $key => $val) {
-        $quoted[$key] = is_null($val) ? 'NULL' : $conn->quote($val);
+
+      if (isset($query['args'])) {
+        foreach ((array) $query['args'] as $key => $val) {
+          $quoted[$key] = is_null($val) ? 'NULL' : $conn->quote($val);
+        }
       }
 
       $query['query_args'] = strtr($query['query'], $quoted);
diff --git a/web/modules/devel/webprofiler/src/DataCollector/EventsDataCollector.php b/web/modules/devel/webprofiler/src/DataCollector/EventsDataCollector.php
index 79493a3f3c..6374ca962f 100644
--- a/web/modules/devel/webprofiler/src/DataCollector/EventsDataCollector.php
+++ b/web/modules/devel/webprofiler/src/DataCollector/EventsDataCollector.php
@@ -2,74 +2,123 @@
 
 namespace Drupal\webprofiler\DataCollector;
 
-use Drupal\webprofiler\DrupalDataCollectorInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
-use Symfony\Component\HttpKernel\DataCollector\EventDataCollector as BaseEventDataCollector;
+use Drupal\webprofiler\DrupalDataCollectorInterface;
+use Drupal\webprofiler\EventDispatcher\EventDispatcherTraceableInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
 
 /**
  * Class EventsDataCollector
  */
-class EventsDataCollector extends BaseEventDataCollector implements DrupalDataCollectorInterface {
+class EventsDataCollector extends DataCollector implements DrupalDataCollectorInterface, LateDataCollectorInterface {
 
   use StringTranslationTrait, DrupalDataCollectorTrait;
 
   /**
-   * @return int
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
    */
-  public function getCalledListenersCount() {
-    return count($this->getCalledListeners());
-  }
+  private $eventDispatcher;
 
   /**
-   * @return int
+   * EventsDataCollector constructor.
+   *
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
    */
-  public function getNotCalledListenersCount() {
-    return count($this->getNotCalledListeners());
+  public function __construct(EventDispatcherInterface $event_dispatcher) {
+    $this->eventDispatcher = $event_dispatcher;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function setCalledListeners(array $listeners) {
-    $listeners = $this->computePriority($listeners);
-    $this->data['called_listeners'] = $listeners;
+  public function collect(Request $request, Response $response, \Exception $exception = NULL) {
+    $this->data = [
+      'called_listeners' => [],
+      'called_listeners_count' => 0,
+      'not_called_listeners' => [],
+      'not_called_listeners_count' => 0,
+    ];
   }
 
   /**
-   * Adds the priority value to the $listeners array.
-   *
-   * @param array $listeners
-   * @return array
+   * {@inheritdoc}
    */
-  private function computePriority(array $listeners) {
-    foreach ($listeners as &$listener) {
-      if (is_subclass_of($listener['class'],  EventSubscriberInterface::class)) {
-        foreach ($listener['class']::getSubscribedEvents() as $event => $methods) {
-
-          if (is_string($methods)) {
-            $methods = [[$methods], 0];
-          }
-          else {
-            if (is_string($methods[0])) {
-              $methods = [$methods];
-            }
+  public function lateCollect() {
+    if ($this->eventDispatcher instanceof EventDispatcherTraceableInterface) {
+      $countCalled = 0;
+      $calledListeners = $this->eventDispatcher->getCalledListeners();
+      foreach ($calledListeners as &$events) {
+        foreach ($events as &$priority) {
+          foreach ($priority as &$listener) {
+            $countCalled++;
+            $listener['clazz'] = $this->getMethodData($listener['class'], $listener['method']);
           }
+        }
+      }
 
-          foreach ($methods as $method) {
-            if ($listener['event'] === $event) {
-              if ($listener['method'] === $method[0]) {
-                $listener['priority'] = isset($method[1]) ? $method[1] : 0;
-              }
-            }
+      $countNotCalled = 0;
+      $notCalledListeners = $this->eventDispatcher->getNotCalledListeners();
+      foreach ($notCalledListeners as $events) {
+        foreach ($events as $priority) {
+          foreach ($priority as $listener) {
+            $countNotCalled++;
           }
         }
-      } else {
-        $listener['priority'] = isset($listener['priority']) ? $listener['priority'] : 0;
       }
+
+      $this->data = [
+        'called_listeners' => $calledListeners,
+        'called_listeners_count' => $countCalled,
+        'not_called_listeners' => $notCalledListeners,
+        'not_called_listeners_count' => $countNotCalled,
+      ];
     }
+  }
+
+  /**
+   * @return array
+   */
+  public function getCalledListeners() {
+    return $this->data['called_listeners'];
+  }
+
+  /**
+   * @return array
+   */
+  public function getNotCalledListeners() {
+    return $this->data['not_called_listeners'];
+  }
+
+  /**
+   * @return int
+   */
+  public function getCalledListenersCount() {
+    return $this->data['called_listeners_count'];
+  }
 
-    return $listeners;
+  /**
+   * @return int
+   */
+  public function getNotCalledListenersCount() {
+    return $this->data['not_called_listeners_count'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getName() {
+    return 'events';
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getData() {
+    return $this->data;
   }
 
   /**
@@ -83,7 +132,7 @@ public function getTitle() {
    * {@inheritdoc}
    */
   public function getPanelSummary() {
-    return $this->t('Called listeners: @listeners', ['@listeners' => count($this->getCalledListeners())]);
+    return $this->t('Called listeners: @listeners', ['@listeners' => $this->getCalledListenersCount()]);
   }
 
   /**
diff --git a/web/modules/devel/webprofiler/src/DataCollector/ServicesDataCollector.php b/web/modules/devel/webprofiler/src/DataCollector/ServicesDataCollector.php
index b7ecb10955..ef653f8051 100644
--- a/web/modules/devel/webprofiler/src/DataCollector/ServicesDataCollector.php
+++ b/web/modules/devel/webprofiler/src/DataCollector/ServicesDataCollector.php
@@ -5,7 +5,7 @@
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\webprofiler\DependencyInjection\TraceableContainer;
 use Drupal\webprofiler\DrupalDataCollectorInterface;
-use Symfony\Component\DependencyInjection\IntrospectableContainerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\DataCollector\DataCollector;
@@ -18,15 +18,15 @@ class ServicesDataCollector extends DataCollector implements DrupalDataCollector
   use StringTranslationTrait, DrupalDataCollectorTrait;
 
   /**
-   * @var \Symfony\Component\DependencyInjection\IntrospectableContainerInterface
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
    *   $container
    */
   private $container;
 
   /**
-   * @param IntrospectableContainerInterface $container
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
    */
-  public function __construct(IntrospectableContainerInterface $container) {
+  public function __construct(ContainerInterface $container) {
     $this->container = $container;
   }
 
diff --git a/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php b/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php
index 168b739c59..b9a06f5528 100644
--- a/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php
+++ b/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ConfigEntityStorageDecorator.php
@@ -108,6 +108,13 @@ public function save(EntityInterface $entity) {
     return $this->getOriginalObject()->save($entity);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function hasData() {
+    return $this->getOriginalObject()->hasData();
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php b/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php
index 1371009d82..7248ba1ce7 100644
--- a/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php
+++ b/web/modules/devel/webprofiler/src/Entity/Decorators/Config/ShortcutSetStorageDecorator.php
@@ -15,42 +15,42 @@ class ShortcutSetStorageDecorator extends ConfigEntityStorageDecorator implement
    * {@inheritdoc}
    */
   public function assignUser(ShortcutSetInterface $shortcut_set, $account) {
-    // TODO: Implement assignUser() method.
+    $this->getOriginalObject()->assignUser($shortcut_set, $account);
   }
 
   /**
    * {@inheritdoc}
    */
   public function unassignUser($account) {
-    // TODO: Implement unassignUser() method.
+    return $this->getOriginalObject()->unassignUser($account);
   }
 
   /**
    * {@inheritdoc}
    */
   public function deleteAssignedShortcutSets(ShortcutSetInterface $entity) {
-    // TODO: Implement deleteAssignedShortcutSets() method.
+    $this->getOriginalObject()->deleteAssignedShortcutSets($entity);
   }
 
   /**
    * {@inheritdoc}
    */
   public function getAssignedToUser($account) {
-    // TODO: Implement getAssignedToUser() method.
+    return $this->getOriginalObject()->getAssignedToUser($account);
   }
 
   /**
    * {@inheritdoc}
    */
   public function countAssignedUsers(ShortcutSetInterface $shortcut_set) {
-    // TODO: Implement countAssignedUsers() method.
+    return $this->getOriginalObject()->countAssignedUsers($shortcut_set);
   }
 
   /**
    * {@inheritdoc}
    */
   public function getDefaultSet(AccountInterface $account) {
-    // TODO: Implement getDefaultSet() method.
+    return $this->getOriginalObject()->getDefaultSet($account);
   }
 
 }
diff --git a/web/modules/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php b/web/modules/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php
new file mode 100644
index 0000000000..dbad151538
--- /dev/null
+++ b/web/modules/devel/webprofiler/src/EventDispatcher/EventDispatcherTraceableInterface.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\webprofiler\EventDispatcher;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+
+interface EventDispatcherTraceableInterface extends EventDispatcherInterface {
+
+  /**
+   * @return array
+   */
+  public function getCalledListeners();
+
+  /**
+   * @return mixed
+   */
+  public function getNotCalledListeners();
+
+}
diff --git a/web/modules/devel/webprofiler/src/EventDispatcher/TraceableEventDispatcher.php b/web/modules/devel/webprofiler/src/EventDispatcher/TraceableEventDispatcher.php
new file mode 100644
index 0000000000..b3451fb508
--- /dev/null
+++ b/web/modules/devel/webprofiler/src/EventDispatcher/TraceableEventDispatcher.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace Drupal\webprofiler\EventDispatcher;
+
+use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
+use Drupal\webprofiler\Stopwatch;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Class TraceableEventDispatcher
+ */
+class TraceableEventDispatcher extends ContainerAwareEventDispatcher implements EventDispatcherTraceableInterface {
+
+  /**
+   * @var \Drupal\webprofiler\Stopwatch
+   *   The stopwatch service.
+   */
+  protected $stopwatch;
+
+  /**
+   * @var array
+   */
+  protected $calledListeners;
+
+  /**
+   * @var array
+   */
+  protected $notCalledListeners;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(ContainerInterface $container, array $listeners = []) {
+    parent::__construct($container, $listeners);
+    $this->notCalledListeners = $listeners;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addListener($event_name, $listener, $priority = 0) {
+    parent::addListener($event_name, $listener, $priority);
+    $this->notCalledListeners[$event_name][$priority][] = ['callable' => $listener];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dispatch($event_name, Event $event = NULL) {
+    if ($event === NULL) {
+      $event = new Event();
+    }
+
+    $this->preDispatch($event_name, $event);
+    $e = $this->stopwatch->start($event_name, 'section');
+
+    if (isset($this->listeners[$event_name])) {
+      // Sort listeners if necessary.
+      if (isset($this->unsorted[$event_name])) {
+        krsort($this->listeners[$event_name]);
+        unset($this->unsorted[$event_name]);
+      }
+
+      // Invoke listeners and resolve callables if necessary.
+      foreach ($this->listeners[$event_name] as $priority => &$definitions) {
+        foreach ($definitions as $key => &$definition) {
+          if (!isset($definition['callable'])) {
+            $definition['callable'] = [
+              $this->container->get($definition['service'][0]),
+              $definition['service'][1],
+            ];
+          }
+
+          $definition['callable']($event, $event_name, $this);
+
+          $this->addCalledListener($definition, $event_name, $priority);
+
+          if ($event->isPropagationStopped()) {
+            return $event;
+          }
+        }
+      }
+    }
+
+    if ($e->isStarted()) {
+      $e->stop();
+    }
+
+    $this->postDispatch($event_name, $event);
+
+    return $event;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCalledListeners() {
+    return $this->calledListeners;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getNotCalledListeners() {
+    return $this->notCalledListeners;
+  }
+
+  /**
+   * @param \Drupal\webprofiler\Stopwatch $stopwatch
+   */
+  public function setStopwatch(Stopwatch $stopwatch) {
+    $this->stopwatch = $stopwatch;
+  }
+
+  /**
+   * Called before dispatching the event.
+   *
+   * @param string $eventName The event name
+   * @param Event $event The event
+   */
+  protected function preDispatch($eventName, Event $event) {
+    switch ($eventName) {
+      case KernelEvents::VIEW:
+      case KernelEvents::RESPONSE:
+        // stop only if a controller has been executed
+        if ($this->stopwatch->isStarted('controller')) {
+          $this->stopwatch->stop('controller');
+        }
+        break;
+    }
+  }
+
+  /**
+   * Called after dispatching the event.
+   *
+   * @param string $eventName The event name
+   * @param Event $event The event
+   */
+  protected function postDispatch($eventName, Event $event) {
+    switch ($eventName) {
+      case KernelEvents::CONTROLLER:
+        $this->stopwatch->start('controller', 'section');
+        break;
+      case KernelEvents::RESPONSE:
+        $token = $event->getResponse()->headers->get('X-Debug-Token');
+        try {
+          $this->stopwatch->stopSection($token);
+        }
+        catch (\LogicException $e) {
+        }
+        break;
+      case KernelEvents::TERMINATE:
+        // In the special case described in the `preDispatch` method above, the `$token` section
+        // does not exist, then closing it throws an exception which must be caught.
+        $token = $event->getResponse()->headers->get('X-Debug-Token');
+        try {
+          $this->stopwatch->stopSection($token);
+        }
+        catch (\LogicException $e) {
+        }
+        break;
+    }
+  }
+
+  /**
+   * @param $definition
+   * @param $event_name
+   * @param $priority
+   */
+  private function addCalledListener($definition, $event_name, $priority) {
+    $this->calledListeners[$event_name][$priority][] = [
+      'class' => get_class($definition['callable'][0]),
+      'method' => $definition['callable'][1],
+    ];
+
+    foreach ($this->notCalledListeners[$event_name][$priority] as $key => $listener) {
+      if (isset($listener['service'])) {
+        if ($listener['service'][0] == $definition['service'][0] && $listener['service'][1] == $definition['service'][1]) {
+          unset($this->notCalledListeners[$event_name][$priority][$key]);
+        }
+      }
+      else {
+        if (get_class($listener['callable'][0]) == get_class($definition['callable'][0]) && $listener['callable'][1] == $definition['callable'][1]) {
+          unset($this->notCalledListeners[$event_name][$priority][$key]);
+        }
+      }
+
+    }
+  }
+
+}
diff --git a/web/modules/devel/webprofiler/src/Form/ConfigForm.php b/web/modules/devel/webprofiler/src/Form/ConfigForm.php
index ae7c601268..e99e2168ca 100644
--- a/web/modules/devel/webprofiler/src/Form/ConfigForm.php
+++ b/web/modules/devel/webprofiler/src/Form/ConfigForm.php
@@ -134,7 +134,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#states' => array(
         'visible' => array(
           array(
-            ':input[name="active_toolbar_items[database]' => array('checked' => TRUE),
+            'input[name="active_toolbar_items[database]"]' => array('checked' => TRUE),
           ),
         ),
       ),
diff --git a/web/modules/devel/webprofiler/src/State/StateWrapper.php b/web/modules/devel/webprofiler/src/State/StateWrapper.php
index f42e66f358..23f930ad40 100644
--- a/web/modules/devel/webprofiler/src/State/StateWrapper.php
+++ b/web/modules/devel/webprofiler/src/State/StateWrapper.php
@@ -2,13 +2,14 @@
 
 namespace Drupal\webprofiler\State;
 
+use Drupal\Core\Cache\CacheCollector;
 use Drupal\Core\State\StateInterface;
 use Drupal\webprofiler\DataCollector\StateDataCollector;
 
 /**
  * Class StateWrapper.
  */
-class StateWrapper implements StateInterface {
+class StateWrapper extends CacheCollector implements StateInterface {
 
   /**
    * The system state.
@@ -92,6 +93,20 @@ public function resetCache() {
     $this->state->resetCache();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function resolveCacheMiss($key) {
+    return $this->state->resolveCacheMiss($key);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function destruct() {
+    $this->updateCache();
+  }
+
   /**
    * Passes through all non-tracked calls onto the decorated object.
    *
diff --git a/web/modules/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php b/web/modules/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php
index 7b427f1804..8a96b08df1 100644
--- a/web/modules/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php
+++ b/web/modules/devel/webprofiler/src/Theme/ThemeNegotiatorWrapper.php
@@ -19,7 +19,25 @@ class ThemeNegotiatorWrapper extends ThemeNegotiator {
    * {@inheritdoc}
    */
   public function determineActiveTheme(RouteMatchInterface $route_match) {
-    foreach ($this->getSortedNegotiators() as $negotiator) {
+    // This method has changed in Drupal 8.4.x, to maintain compatibility with
+    // Drupal 8.3.x we check the existence or not of the classResolver
+    // property.
+    // TODO: remove this logic when we decide to drop Drupal 8.3.x support.
+    if (property_exists($this, 'classResolver')) {
+      $classResolver = $this->classResolver;
+      $negotiators = $this->negotiators;
+    } else {
+      $classResolver = \Drupal::classResolver();
+      $negotiators = $this->getSortedNegotiators();
+    }
+
+    foreach ($negotiators as $negotiator_id) {
+      if (property_exists($this, 'classResolver')) {
+        $negotiator = $classResolver->getInstanceFromDefinition($negotiator_id);
+      } else {
+        $negotiator = $negotiator_id;
+      }
+
       if ($negotiator->applies($route_match)) {
         $theme = $negotiator->determineActiveTheme($route_match);
         if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) {
@@ -36,4 +54,27 @@ public function determineActiveTheme(RouteMatchInterface $route_match) {
   public function getNegotiator() {
     return $this->negotiator;
   }
+
+  /**
+   * Returns the sorted array of theme negotiators.
+   *
+   * @return array|\Drupal\Core\Theme\ThemeNegotiatorInterface[]
+   *   An array of theme negotiator objects.
+   *
+   * TODO: remove this method when we decide to drop Drupal 8.3.x support.
+   */
+  protected function getSortedNegotiators() {
+    if (!isset($this->sortedNegotiators)) {
+      // Sort the negotiators according to priority.
+      krsort($this->negotiators);
+      // Merge nested negotiators from $this->negotiators into
+      // $this->sortedNegotiators.
+      $this->sortedNegotiators = [];
+      foreach ($this->negotiators as $builders) {
+        $this->sortedNegotiators = array_merge($this->sortedNegotiators, $builders);
+      }
+    }
+    return $this->sortedNegotiators;
+  }
+
 }
diff --git a/web/modules/devel/webprofiler/src/TraceableEventDispatcher.php b/web/modules/devel/webprofiler/src/TraceableEventDispatcher.php
deleted file mode 100644
index 44c5cf3dfc..0000000000
--- a/web/modules/devel/webprofiler/src/TraceableEventDispatcher.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-
-namespace Drupal\webprofiler;
-
-use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher;
-use Symfony\Component\HttpKernel\KernelEvents;
-use Symfony\Component\EventDispatcher\Event;
-
-/**
- * Class TraceableEventDispatcher
- */
-class TraceableEventDispatcher extends BaseTraceableEventDispatcher {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function preDispatch($eventName, Event $event) {
-    switch ($eventName) {
-      case KernelEvents::VIEW:
-      case KernelEvents::RESPONSE:
-        // stop only if a controller has been executed
-        if ($this->stopwatch->isStarted('controller')) {
-          $this->stopwatch->stop('controller');
-        }
-        break;
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function postDispatch($eventName, Event $event) {
-    switch ($eventName) {
-      case KernelEvents::CONTROLLER:
-        $this->stopwatch->start('controller', 'section');
-        break;
-      case KernelEvents::RESPONSE:
-        $token = $event->getResponse()->headers->get('X-Debug-Token');
-        try {
-          $this->stopwatch->stopSection($token);
-        } catch (\LogicException $e) {
-        }
-        break;
-      case KernelEvents::TERMINATE:
-        // In the special case described in the `preDispatch` method above, the `$token` section
-        // does not exist, then closing it throws an exception which must be caught.
-        $token = $event->getResponse()->headers->get('X-Debug-Token');
-        try {
-          $this->stopwatch->stopSection($token);
-        } catch (\LogicException $e) {
-        }
-        break;
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getListenerPriority($eventName, $listener) {
-    if (!isset($this->listeners[$eventName])) {
-      return;
-    }
-    foreach ($this->listeners[$eventName] as $priority => $listeners) {
-      if (FALSE !== ($key = array_search($listener, $listeners, TRUE))) {
-        return $priority;
-      }
-    }
-  }
-
-}
diff --git a/web/modules/devel/webprofiler/src/Views/TraceableViewExecutable.php b/web/modules/devel/webprofiler/src/Views/TraceableViewExecutable.php
index 3580392da1..afd6e39170 100644
--- a/web/modules/devel/webprofiler/src/Views/TraceableViewExecutable.php
+++ b/web/modules/devel/webprofiler/src/Views/TraceableViewExecutable.php
@@ -31,7 +31,7 @@ public function getBuildTime() {
    * @return float
    */
   public function getExecuteTime() {
-    return $this->execute_time;
+    return property_exists($this, 'execute_time') ? $this->execute_time : 0.0;
   }
 
   /**
diff --git a/web/modules/devel/webprofiler/src/WebprofilerServiceProvider.php b/web/modules/devel/webprofiler/src/WebprofilerServiceProvider.php
index 20b67f0b53..3ba1ff0682 100644
--- a/web/modules/devel/webprofiler/src/WebprofilerServiceProvider.php
+++ b/web/modules/devel/webprofiler/src/WebprofilerServiceProvider.php
@@ -28,7 +28,6 @@ public function register(ContainerBuilder $container) {
     $container->addCompilerPass(new StoragePass());
 
     $container->addCompilerPass(new ServicePass(), PassConfig::TYPE_AFTER_REMOVING);
-    $container->addCompilerPass(new EventPass(), PassConfig::TYPE_AFTER_REMOVING);
     $container->addCompilerPass(new DecoratorPass(), PassConfig::TYPE_AFTER_REMOVING);
 
     $modules = $container->getParameter('container.modules');
@@ -57,7 +56,7 @@ public function register(ContainerBuilder $container) {
           'priority' => 78,
         ]);
     }
-    
+
     // Add TranslationsDataCollector only if Locale module is enabled.
     if (isset($modules['locale'])) {
       $container->register('webprofiler.translations', 'Drupal\webprofiler\DataCollector\TranslationsDataCollector')
@@ -105,5 +104,14 @@ public function alter(ContainerBuilder $container) {
     // Replace the regular string_translation service with a traceable one.
     $container->getDefinition('string_translation')
       ->setClass('Drupal\webprofiler\StringTranslation\TranslationManagerWrapper');
+
+    // Replace the regular event_dispatcher service with a traceable one.
+    $container->getDefinition('event_dispatcher')
+      ->setClass('Drupal\webprofiler\EventDispatcher\TraceableEventDispatcher')
+      ->addMethodCall('setStopwatch', [new Reference('stopwatch')]);
+
+    $container->getDefinition('http_kernel.basic')
+      ->replaceArgument(1, new Reference('webprofiler.debug.controller_resolver'));
   }
+
 }
diff --git a/web/modules/devel/webprofiler/templates/Collector/events.html.twig b/web/modules/devel/webprofiler/templates/Collector/events.html.twig
index 9167413af1..17087e1bff 100644
--- a/web/modules/devel/webprofiler/templates/Collector/events.html.twig
+++ b/web/modules/devel/webprofiler/templates/Collector/events.html.twig
@@ -1,21 +1,22 @@
 {% block toolbar %}
     {% set icon %}
-    <a href="{{ url("webprofiler.dashboard", {profile: token}, {fragment: 'events'}) }}" title="{{ 'Events'|t }}">
-        <img width="20" height="28" alt="{{ 'Events'|t }}"
-             src="data:image/png;base64,{{ collector.icon }}">
-        <span class="sf-toolbar-info-piece-additional sf-toolbar-status">{{ collector.getCalledListenersCount }}</span>
-    </a>
+        <a href="{{ url("webprofiler.dashboard", {profile: token}, {fragment: 'events'}) }}" title="{{ 'Events'|t }}">
+            <img width="20" height="28" alt="{{ 'Events'|t }}"
+                 src="data:image/png;base64,{{ collector.icon }}">
+            <span class="sf-toolbar-info-piece-additional sf-toolbar-status">{{ collector.getCalledListenersCount }}</span>
+        </a>
     {% endset %}
 
     {% set text %}
-    <div class="sf-toolbar-info-piece">
-        <b>{{ 'Triggered'|t }}</b>
-        <span>{{ collector.getCalledListenersCount }}</span>
-    </div>
-    <div class="sf-toolbar-info-piece">
-        <b>{{ 'Not triggered'|t }}</b>
-        <span>{{ collector.getNotCalledListenersCount }}</span>
-    </div>
+        <div class="sf-toolbar-info-piece">
+            <b>{{ 'Called'|t }}</b>
+            <span>{{ collector.getCalledListenersCount }}</span>
+        </div>
+
+        <div class="sf-toolbar-info-piece">
+            <b>{{ 'Not called'|t }}</b>
+            <span>{{ collector.getNotCalledListenersCount }}</span>
+        </div>
     {% endset %}
 
     <div class="sf-toolbar-block">
@@ -36,44 +37,46 @@
                 <th>{{ 'Class'|t }}</th>
                 <th>{{ 'Priority'|t }}</th>
                 </thead>
-                <tbody> <% _.each( data.called_listeners, function( item ){ %>
-                <tr>
-                    <td><%= item.event %></td>
-                    <% if(item.type == 'Method') { %>
-                    <td>
-                        <%= Drupal.webprofiler.helpers.classLink(item) %>
-                    </td>
-                    <% } else { %>
-                    <td>{{ 'Closure'|t }}</td>
-                    <% } %>
-                    <td><%= item.priority %></td>
-                </tr>
+                <tbody>
+                <% _.each( data.called_listeners, function( events, event_name ){ %>
+                    <% _.each( events, function( priority, priority_value ){ %>
+                        <% _.each( priority, function( listener ){ %>
+                            <tr>
+                                <td><%= event_name %></td>
+                                <% if( listener.clazz ) { %>
+                                    <td><%= Drupal.webprofiler.helpers.classLink(listener.clazz) %></td>
+                                <% } else { %>
+                                    <td><%= listener.service[0] %>::<%= listener.service[1] %></td>
+                                <% } %>
+                                <td><%= priority_value %></td>
+                            </tr>
+                        <% }); %>
+                    <% }); %>
                 <% }); %>
                 </tbody>
             </table>
-        </div>
-        <div class="panel__container">
 
             <table class="table--duo">
                 <thead>
-                <th>{{ 'Non called listeners'|t }}</th>
-                <th>{{ 'Class'|t }}</th>
+                <th>{{ 'Not called listeners'|t }}</th>
+                <th>{{ 'Service'|t }}</th>
+                <th>{{ 'Priority'|t }}</th>
                 </thead>
                 <tbody>
-                <% _.each( data.not_called_listeners, function( item ){ %>
+                <% _.each( data.not_called_listeners, function( events, event_name ){ %>
+                <% _.each( events, function( priority, priority_value ){ %>
+                <% _.each( priority, function( listener ){ %>
                 <tr>
-                    <td><%= item.event %></td>
-                    <% if(item.type == 'Method') { %>
-                    <td>
-                        <%= Drupal.webprofiler.helpers.classLink(item) %>
-                    </td>
-                    <% } else { %>
-                    <td>{{ 'Closure'|t }}</td>
-                    <% } %>
+                    <td><%= event_name %></td>
+                    <td><%= listener.service[0] %>::<%= listener.service[1] %></td>
+                    <td><%= priority_value %></td>
                 </tr>
                 <% }); %>
+                <% }); %>
+                <% }); %>
                 </tbody>
             </table>
         </div>
+
     </script>
 {% endblock %}
diff --git a/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php b/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php
new file mode 100644
index 0000000000..84c7d820f3
--- /dev/null
+++ b/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/ToolbarTest.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\Tests\webprofiler\FunctionalJavascript;
+
+/**
+ * Tests the JavaScript functionality of webprofiler.
+ *
+ * @group webprofiler
+ */
+class ToolbarTest extends WebprofilerTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['webprofiler', 'node'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    \Drupal::configFactory()->getEditable('system.site')->set('page.front', '/node')->save(TRUE);
+  }
+
+  /**
+   * Tests if the toolbar appears on front page.
+   */
+  public function testToolbarOnFrontPage() {
+    $this->loginForToolbar();
+
+    $this->drupalGet('<front>');
+
+    $this->waitForToolbar();
+
+    $assert = $this->assertSession();
+    $assert->pageTextContains(\Drupal::VERSION);
+    $assert->pageTextContains('Configure Webprofiler');
+    $assert->pageTextContains('View latest reports');
+    $assert->pageTextContains('Drupal Documentation');
+    $assert->pageTextContains('Get involved!');
+  }
+
+  /**
+   * Tests the toolbar report page.
+   */
+  public function testToolbarReportPage() {
+    $this->loginForDashboard();
+
+    $this->drupalGet('<front>');
+
+    $token = $this->waitForToolbar();
+
+    $this->drupalGet('admin/reports/profiler/list');
+
+    $assert = $this->assertSession();
+    $assert->pageTextContains($token);
+  }
+
+  /**
+   * Tests the toolbar not appears on excluded path.
+   */
+  public function testToolbarNotAppearsOnExcludedPath() {
+    $this->loginForDashboard();
+
+    $this->drupalGet('admin/config/development/devel');
+    $token = $this->waitForToolbar();
+    $assert = $this->assertSession();
+    $assert->pageTextContains($token);
+    $assert->pageTextContains('Configure Webprofiler');
+
+    $this->config('webprofiler.config')
+      ->set('exclude', '/admin/config/development/devel')
+      ->save();
+    $this->drupalGet('admin/config/development/devel');
+    $this->assertSession()->pageTextNotContains('sf-toolbar');
+  }
+
+}
diff --git a/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php b/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php
new file mode 100644
index 0000000000..b38a1f21e9
--- /dev/null
+++ b/web/modules/devel/webprofiler/tests/src/FunctionalJavascript/WebprofilerTestBase.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\Tests\webprofiler\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use PHPUnit_Framework_AssertionFailedError;
+
+/**
+ * Class WebprofilerTestBase.
+ *
+ * @group webprofiler
+ */
+abstract class WebprofilerTestBase extends JavascriptTestBase {
+
+  /**
+   * Wait until the toolbar is present on page.
+   */
+  protected function waitForToolbar() {
+    $session = $this->getSession();
+    $token = $this->getToken();
+    $page = $session->getPage();
+
+    $toolbar = $page->findById('webprofiler' . $token);
+    $this->assertTrue($toolbar->hasClass('sf-toolbar'), 'Toolbar loader is present in page');
+
+    $session->wait(1000, 'null !== document.getElementById(\'sfToolbarMainContent-' . $token . '\')');
+
+    return $token;
+  }
+
+  /**
+   * Return the Webprofiler token.
+   *
+   * @return null|string
+   *   The page token
+   */
+  protected function getToken() {
+    $token = $this->getSession()->getResponseHeader('X-Debug-Token');
+
+    if (NULL === $token) {
+      throw new PHPUnit_Framework_AssertionFailedError();
+    }
+
+    return $token;
+  }
+
+  /**
+   * Login with a user that can see the toolbar.
+   */
+  protected function loginForToolbar() {
+    $admin_user = $this->drupalCreateUser(
+      [
+        'view webprofiler toolbar',
+      ]
+    );
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Login with a user that can see the toolbar and the dashboard.
+   */
+  protected function loginForDashboard() {
+    $admin_user = $this->drupalCreateUser(
+      [
+        'view webprofiler toolbar',
+        'access webprofiler',
+      ]
+    );
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Flush cache.
+   */
+  protected function flushCache() {
+    $module_handler = \Drupal::moduleHandler();
+    $module_handler->invokeAll('cache_flush');
+  }
+
+}
diff --git a/web/modules/devel/webprofiler/tests/src/Kernel/DecoratorTest.php b/web/modules/devel/webprofiler/tests/src/Kernel/DecoratorTest.php
new file mode 100644
index 0000000000..a557c08f47
--- /dev/null
+++ b/web/modules/devel/webprofiler/tests/src/Kernel/DecoratorTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\Tests\webprofiler\Kernel;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Class DecoratorTest.
+ *
+ * @group webprofiler
+ */
+class DecoratorTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['system', 'views'];
+
+  /**
+   * Tests the Entity Type Manager service decoration.
+   *
+   * @param string $service
+   *   The service name.
+   * @param string $original
+   *   The original class.
+   * @param string $decorated
+   *   The decorated class.
+   *
+   * @dataProvider decorators
+   */
+  public function testEntityTypeDecorator($service, $original, $decorated) {
+    $entityTypeManagerOriginal = $this->container->get($service);
+
+    $this->assertInstanceOf($original, $entityTypeManagerOriginal);
+
+    $this->container->get('module_installer')->install(['webprofiler']);
+
+    $entityTypeManagerDecorated = $this->container->get($service);
+
+    $this->assertInstanceOf($decorated, $entityTypeManagerDecorated);
+  }
+
+  /**
+   * DataProvider for testEntityTypeDecorator.
+   *
+   * @return array
+   *   The array of values to run tests on.
+   */
+  public function decorators() {
+    return array(
+      array('entity_type.manager', 'Drupal\Core\Entity\EntityTypeManager', 'Drupal\webprofiler\Entity\EntityManagerWrapper'),
+      array('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory', 'Drupal\webprofiler\Cache\CacheFactoryWrapper'),
+      array('asset.css.collection_renderer', 'Drupal\Core\Asset\CssCollectionRenderer', 'Drupal\webprofiler\Asset\CssCollectionRendererWrapper'),
+      array('asset.js.collection_renderer', 'Drupal\Core\Asset\JsCollectionRenderer', 'Drupal\webprofiler\Asset\JsCollectionRendererWrapper'),
+      array('state', 'Drupal\Core\State\State', 'Drupal\webprofiler\State\StateWrapper'),
+      array('views.executable', 'Drupal\views\ViewExecutableFactory', 'Drupal\webprofiler\Views\ViewExecutableFactoryWrapper'),
+      array('form_builder', 'Drupal\Core\Form\FormBuilder', 'Drupal\webprofiler\Form\FormBuilderWrapper'),
+      array('access_manager', 'Drupal\Core\Access\AccessManager', 'Drupal\webprofiler\Access\AccessManagerWrapper'),
+      array('theme.negotiator', 'Drupal\Core\Theme\ThemeNegotiator', 'Drupal\webprofiler\Theme\ThemeNegotiatorWrapper'),
+      array('config.factory', 'Drupal\Core\Config\ConfigFactory', 'Drupal\webprofiler\Config\ConfigFactoryWrapper'),
+      array('string_translation', 'Drupal\Core\StringTranslation\TranslationManager', 'Drupal\webprofiler\StringTranslation\TranslationManagerWrapper'),
+    );
+  }
+
+}
diff --git a/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php
new file mode 100644
index 0000000000..37d6ac3ee5
--- /dev/null
+++ b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/AssetsDataCollectorTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Tests\webprofiler\Unit\DataCollector;
+
+use Drupal\webprofiler\Asset\CssCollectionRendererWrapper;
+use Drupal\webprofiler\DataCollector\AssetsDataCollector;
+
+/**
+ * @coversDefaultClass \Drupal\webprofiler\DataCollector\AssetsDataCollector
+ *
+ * @group webprofiler
+ */
+class AssetsDataCollectorTest extends DataCollectorBaseTest {
+
+  /**
+   * @var \Drupal\webprofiler\DataCollector\AssetsDataCollector
+   */
+  private $assetDataCollector;
+
+  /**
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  private $assetCollectionRendererInterface;
+
+  const ROOT = 'test_root';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->assetDataCollector = new AssetsDataCollector(AssetsDataCollectorTest::ROOT);
+    $this->assetCollectionRendererInterface = $this->getMock('Drupal\Core\Asset\AssetCollectionRendererInterface');
+  }
+
+  /**
+   * Tests the Assets data collector.
+   */
+  public function testCSS() {
+    $css = [
+      'core/assets/vendor/normalize-css/normalize.css' => [
+        'weight' => -219.944,
+        'group' => 0,
+        'type' => 'file',
+        'data' => 'core\/assets\/vendor\/normalize-css\/normalize.css',
+        'version' => '3.0.3',
+        'media' => 'all',
+        'preprocess' => TRUE,
+        'browsers' => [
+          'IE' => TRUE,
+          '!IE' => TRUE,
+        ],
+      ],
+    ];
+
+    $cssCollectionRendererWrapper = new CssCollectionRendererWrapper($this->assetCollectionRendererInterface, $this->assetDataCollector);
+    $cssCollectionRendererWrapper->render($css);
+
+    $this->assertEquals(1, $this->assetDataCollector->getCssCount());
+
+    $this->assetDataCollector->collect($this->request, $this->response, $this->exception);
+
+    $data = $this->assetDataCollector->getData();
+    $this->assertEquals(AssetsDataCollectorTest::ROOT . '/', $data['assets']['installation_path']);
+  }
+
+}
diff --git a/web/modules/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php
similarity index 91%
rename from web/modules/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php
rename to web/modules/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php
index f4673eea7d..d51e604dd6 100644
--- a/web/modules/devel/webprofiler/tests/src/Unit/Cache/CacheDataCollectorTest.php
+++ b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/CacheDataCollectorTest.php
@@ -1,16 +1,16 @@
 <?php
 
-namespace Drupal\Tests\webprofiler\Unit\Cache;
+namespace Drupal\Tests\webprofiler\Unit\DataCollector;
 
-use Drupal\Tests\UnitTestCase;
 use Drupal\webprofiler\Cache\CacheBackendWrapper;
 use Drupal\webprofiler\DataCollector\CacheDataCollector;
 
 /**
  * @coversDefaultClass \Drupal\webprofiler\DataCollector\CacheDataCollector
+ *
  * @group webprofiler
  */
-class CacheDataCollectorTest extends UnitTestCase {
+class CacheDataCollectorTest extends DataCollectorBaseTest {
 
   /**
    * @var \Drupal\webprofiler\DataCollector\CacheDataCollector
@@ -50,7 +50,7 @@ public function testCacheCollectorMiss() {
    * Tests the collection of a cache hit.
    */
   public function testCacheCollectorHit() {
-    $cache = new \StdClass();
+    $cache = new \stdClass();
     $cache->cid = 'cache_id';
     $cache->expire = 1;
     $cache->tags = ['tag1', 'tag2'];
diff --git a/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php
new file mode 100644
index 0000000000..8d62e7809e
--- /dev/null
+++ b/web/modules/devel/webprofiler/tests/src/Unit/DataCollector/DataCollectorBaseTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Tests\webprofiler\Unit\DataCollector;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Class DataCollectorBaseTest.
+ *
+ * @group webprofiler
+ */
+abstract class DataCollectorBaseTest extends UnitTestCase {
+
+  /**
+   * @var
+   */
+  protected $request;
+
+  /**
+   * @var
+   */
+  protected $response;
+
+  /**
+   * @var
+   */
+  protected $exception;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->request = $this->getMock('Symfony\Component\HttpFoundation\Request');
+    $this->response = $this->getMock('Symfony\Component\HttpFoundation\Response');
+    $this->exception = $this->getMock('Exception');
+  }
+
+}
diff --git a/web/modules/devel/webprofiler/webprofiler.info.yml b/web/modules/devel/webprofiler/webprofiler.info.yml
index 903971555a..8aebae2313 100644
--- a/web/modules/devel/webprofiler/webprofiler.info.yml
+++ b/web/modules/devel/webprofiler/webprofiler.info.yml
@@ -9,8 +9,8 @@ tags:
 dependencies:
  - devel
 
-# Information added by Drupal.org packaging script on 2017-04-23
-version: '8.x-1.0-rc2'
+# Information added by Drupal.org packaging script on 2017-10-05
+version: '8.x-1.2'
 core: '8.x'
 project: 'devel'
-datestamp: 1492989248
+datestamp: 1507197848
diff --git a/web/modules/devel/webprofiler/webprofiler.routing.yml b/web/modules/devel/webprofiler/webprofiler.routing.yml
index 200bc39734..11543ecb90 100644
--- a/web/modules/devel/webprofiler/webprofiler.routing.yml
+++ b/web/modules/devel/webprofiler/webprofiler.routing.yml
@@ -22,7 +22,7 @@ webprofiler.frontend.save:
         type: 'webprofiler:token'
   methods:  [POST]
   requirements:
-    _permission: 'access webprofiler'
+    _permission: 'view webprofiler toolbar'
 
 # view profile
 webprofiler.dashboard:
diff --git a/web/modules/devel/webprofiler/webprofiler.services.yml b/web/modules/devel/webprofiler/webprofiler.services.yml
index 8d6e618e53..6332fef926 100644
--- a/web/modules/devel/webprofiler/webprofiler.services.yml
+++ b/web/modules/devel/webprofiler/webprofiler.services.yml
@@ -47,7 +47,7 @@ services:
 # event subscribers
   webprofiler.profiler_listener:
     class: Symfony\Component\HttpKernel\EventListener\ProfilerListener
-    arguments: ['@profiler', '@?webprofiler.matcher', '%webprofiler.only_exceptions%', '%webprofiler.only_master_requests%', '@request_stack']
+    arguments: ['@profiler', '@request_stack', '@?webprofiler.matcher', '%webprofiler.only_exceptions%', '%webprofiler.only_master_requests%']
     tags:
       - { name: event_subscriber }
 
@@ -187,10 +187,6 @@ services:
   stopwatch:
     class: Drupal\webprofiler\Stopwatch
 
-  webprofiler.debug.event_dispatcher.default:
-    class: Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher
-    arguments: ['@service_container']
-
   webprofiler.debug.plugin.manager.mail.default:
     class: Drupal\Core\Mail\MailManager
     arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@config.factory', '@logger.factory', '@string_translation', '@renderer']
-- 
GitLab