diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..51b24f41e592e08e83739a34d680e24d38d71a4f
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,308 @@
+# cspell:ignore codequality Micheh micheh
+
+################
+# Drupal GitLabCI template.
+#
+# Based off GitlabCI templates project: https://git.drupalcode.org/project/gitlab_templates
+# Guide: https://www.drupal.org/docs/develop/git/using-gitlab-to-contribute-to-drupal/gitlab-ci
+#
+# With thanks to:
+# - The GitLab Acceleration Initiative participants
+# - DrupalSpoons
+################
+
+################
+# Includes
+#
+# Additional configuration can be provided through includes.
+# One advantage of include files is that if they are updated upstream, the
+# changes affect all pipelines using that include.
+#
+# Includes can be overridden by re-declaring anything provided in an include,
+# here in gitlab-ci.yml.
+# https://docs.gitlab.com/ee/ci/yaml/includes.html#override-included-configuration-values
+################
+
+include:
+  - project: $_GITLAB_TEMPLATES_REPO
+    ref: $_GITLAB_TEMPLATES_REF
+    file:
+      - '/includes/include.drupalci.variables.yml'
+      - '/includes/include.drupalci.workflows.yml'
+
+################
+# Variables
+#
+# Overriding variables
+# - To override one or more of these variables, simply declare your own variables keyword.
+# - Keywords declared directly in .gitlab-ci.yml take precedence over include files.
+# - Documentation:  https://docs.gitlab.com/ee/ci/variables/
+# - Predefined variables: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
+#
+################
+
+variables:
+  COMPOSER: composer.json
+  # Let composer know what self.version means.
+  COMPOSER_ROOT_VERSION: "${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}${CI_COMMIT_BRANCH}-dev"
+  CONCURRENCY: 24
+  GIT_DEPTH: "3"
+
+
+#############
+# Stages    #
+#############
+stages:
+  - 🏗️ Build
+  - 🪄 Lint
+  - 🗜️ Test
+
+#############
+# Templates #
+#############
+
+.default-job-settings: &default-job-settings-lint
+  interruptible: true
+  allow_failure: false
+  retry:
+    max: 2
+    when:
+      - unknown_failure
+      - api_failure
+      - stuck_or_timeout_failure
+      - runner_system_failure
+      - scheduler_failure
+  variables:
+    _TARGET_PHP: "8.2"
+    _TARGET_DB: "sqlite-3"
+  image:
+    name: $_CONFIG_DOCKERHUB_ROOT/php-$_TARGET_PHP-apache:production
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ROOT_NAMESPACE == "project"
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+.composer-cache: &composer-cache
+  key:
+    files:
+      - ./composer.json
+      - ./composer.lock
+  paths:
+    - ./vendor
+
+.yarn-cache: &yarn-cache
+  key:
+    files:
+      - ./core/package.json
+      - ./core/yarn.lock
+  paths:
+    - ./core/node_modules
+
+.with-composer-cache: &with-composer-cache
+  cache:
+    policy: pull
+    <<: *composer-cache
+  needs:
+    - '📦️ Composer'
+
+.with-yarn-cache: &with-yarn-cache
+  needs:
+    - '📦️ Yarn'
+  cache:
+    policy: pull
+    <<: *yarn-cache
+
+.junit-artifacts: &junit-artifacts
+  artifacts:
+    expose_as: junit
+    expire_in: 6 mos
+    paths:
+      - junit.xml
+    reports:
+      junit: junit.xml
+
+
+################
+# Stages
+#
+# Each job is assigned to a stage, defining the order in which the jobs are executed.
+# Jobs in the same stage run in parallel.
+#
+# If all jobs in a stage succeed, the pipeline will proceed to the next stage.
+# If any job in the stage fails, the pipeline will exit early.
+################
+
+.default-stage: &default-stage
+  stage: 🗜️ Test
+  trigger:
+    # Rely on the status of the child pipeline.
+    strategy: depend
+    include:
+      - local: .gitlab-ci/pipeline.yml
+
+.run-on-commit: &run-on-commit
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ROOT_NAMESPACE == "project"
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+      when: manual
+      allow_failure: true
+
+.run-daily: &run-daily
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "schedule" && $CI_PROJECT_ROOT_NAMESPACE == "project"
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+      when: manual
+      allow_failure: true
+
+# Default configuration.
+'PHP 8.2 MySQL 8':
+  <<: *default-stage
+  variables:
+    _TARGET_PHP: "8.2"
+    _TARGET_DB: "mysql-8"
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "push" && $CI_PROJECT_ROOT_NAMESPACE == "project"
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+
+# Run on commit, or manually.
+'PHP 8.1 MySQL 5.7':
+  <<: [ *default-stage, *run-on-commit ]
+  variables:
+    _TARGET_PHP: "8.1"
+    _TARGET_DB: "mysql-5.7"
+
+'PHP 8.2 PostgreSQL 14.1':
+  <<: [ *default-stage, *run-daily ]
+  variables:
+    _TARGET_PHP: "8.2"
+    _TARGET_DB: "pgsql-14.1"
+
+'PHP 8.2 PostgreSQL 15':
+  <<: [ *default-stage, *run-daily ]
+  variables:
+    _TARGET_PHP: "8.2"
+    _TARGET_DB: "pgsql-15"
+
+'PHP 8.2 PostgreSQL 16':
+  <<: [ *default-stage, *run-on-commit ]
+  variables:
+    _TARGET_PHP: "8.2"
+    _TARGET_DB: "pgsql-16"
+
+'PHP 8.2 SQLite 3':
+  <<: [ *default-stage, *run-on-commit ]
+  variables:
+    _TARGET_PHP: "8.2"
+    _TARGET_DB: "sqlite-3"
+
+# Run daily, or manually.
+# 'PHP 8.1 MariaDB 10.3.22':
+#   <<: [ *default-stage, *run-daily ]
+#   variables:
+#     _TARGET_PHP: "8.1"
+#     _TARGET_DB: "mariadb-10.3.22"
+
+# 'PHP 8.1 MySQL 5.7 with updated deps':
+#   <<: [ *default-stage, *run-daily ]
+#   variables:
+#     _TARGET_PHP: "8.1"
+#     _TARGET_DB: "mysql-5.7"
+
+'PHP 8.1 PostgreSQL 14.1':
+  <<: [ *default-stage, *run-daily ]
+  variables:
+    _TARGET_PHP: "8.1"
+    _TARGET_DB: "pgsql-14.1"
+
+'PHP 8.1 SQLite 3':
+  <<: [ *default-stage, *run-daily ]
+  variables:
+    _TARGET_PHP: "8.1"
+    _TARGET_DB: "sqlite-3"
+
+
+################
+# Build Jobs for linting
+################
+
+'📦️ Composer':
+  <<: *default-job-settings-lint
+  stage: 🏗️ Build
+  cache:
+    <<: *composer-cache
+  artifacts:
+    expire_in: 1 week
+    expose_as: 'web-vendor'
+    paths:
+      - vendor/
+  script:
+      - composer validate
+      - composer install
+
+'📦️ Yarn':
+  <<: *default-job-settings-lint
+  stage: 🏗️ Build
+  cache:
+    <<: *yarn-cache
+  artifacts:
+    expire_in: 1 week
+    expose_as: 'yarn-vendor'
+    paths:
+      - core/node_modules/
+  script:
+    - yarn --cwd ./core install
+
+################
+# Lint Jobs
+################
+
+'🧹 PHP Static Analysis (phpstan)':
+  <<: [ *with-composer-cache, *junit-artifacts, *default-job-settings-lint ]
+  stage: 🪄 Lint
+  script:
+    - php vendor/bin/phpstan analyze --configuration=./core/phpstan.neon.dist --error-format=gitlab > phpstan-quality-report.json
+  artifacts:
+    reports:
+      codequality: phpstan-quality-report.json
+
+'🧹 PHP Coding standards (PHPCS)':
+  <<: [ *with-composer-cache, *junit-artifacts, *default-job-settings-lint ]
+  stage: 🪄 Lint
+  script:
+    - composer phpcs -- --report-full --report-summary --report-\\Micheh\\PhpCodeSniffer\\Report\\Gitlab=phpcs-quality-report.json
+  artifacts:
+    reports:
+      codequality: phpcs-quality-report.json
+
+'🧹 JavaScript linting (eslint)':
+  <<: [ *with-yarn-cache, *junit-artifacts, *default-job-settings-lint ]
+  stage: 🪄 Lint
+  script:
+    - yarn --cwd=./core run -s lint:core-js-passing --format gitlab
+  artifacts:
+    reports:
+      codequality: eslint-quality-report.json
+
+'🧹 CSS linting (stylelint)':
+  <<: [ *with-yarn-cache, *junit-artifacts, *default-job-settings-lint ]
+  stage: 🪄 Lint
+  script:
+    - yarn run --cwd=./core lint:css --color --custom-formatter=node_modules/stylelint-formatter-gitlab
+  artifacts:
+    reports:
+      codequality: stylelint-quality-report.json
+
+'🧹 Compilation check':
+  <<: [ *with-yarn-cache, *default-job-settings-lint ]
+  stage: 🪄 Lint
+  script:
+    - yarn run --cwd=./core build:css --check
+    - cd core && yarn run -s check:ckeditor5
+
+'📔 Spell-checking':
+  <<: [ *with-yarn-cache, *default-job-settings-lint ]
+  stage: 🪄 Lint
+  script:
+    - export TARGET_BRANCH=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}${CI_COMMIT_BRANCH}
+    - git fetch -vn --depth=$GIT_DEPTH "${CI_MERGE_REQUEST_PROJECT_URL:-origin}" "+refs/heads/$TARGET_BRANCH:refs/heads/$TARGET_BRANCH"
+    - export MODIFIED=`git diff --name-only refs/heads/$TARGET_BRANCH|while read r;do echo "$CI_PROJECT_DIR/$r";done|tr "\n" " "`
+    - echo $MODIFIED | tr ' ' '\n' | yarn --cwd=./core run -s spellcheck:core --no-must-find-files --file-list stdin
diff --git a/.gitlab-ci/pipeline.yml b/.gitlab-ci/pipeline.yml
new file mode 100644
index 0000000000000000000000000000000000000000..08952b0041d204f8fb7eff80e9499b8973468163
--- /dev/null
+++ b/.gitlab-ci/pipeline.yml
@@ -0,0 +1,329 @@
+# cspell:ignore drupaltestbot drupaltestbotpw
+
+stages:
+  ################
+  # Build
+  #
+  # Assemble the test environment.
+  ################
+  - 🏗️ Build
+
+  ################
+  # Test
+  #
+  # The test phase actually executes the tests, as well as gathering results
+  # and artifacts.
+  ################
+  - 🗜️ Test
+
+#############
+# Templates #
+#############
+
+.default-job-settings: &default-job-settings
+  interruptible: true
+  allow_failure: false
+  retry:
+    max: 2
+    when:
+      - unknown_failure
+      - api_failure
+      - stuck_or_timeout_failure
+      - runner_system_failure
+      - scheduler_failure
+  image:
+    name: $_CONFIG_DOCKERHUB_ROOT/php-$_TARGET_PHP-apache:production
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "parent_pipeline"
+
+.composer-cache: &composer-cache
+  key:
+    files:
+      - ./composer.json
+      - ./composer.lock
+  paths:
+    - ./vendor
+
+.yarn-cache: &yarn-cache
+  key:
+    files:
+      - ./core/package.json
+      - ./core/yarn.lock
+  paths:
+    - ./core/node_modules
+
+.with-composer-cache: &with-composer-cache
+  needs:
+    - '📦️ Composer'
+  cache:
+    policy: pull
+    <<: *composer-cache
+
+.with-yarn-cache: &with-yarn-cache
+  needs:
+    - '📦️ Yarn'
+  cache:
+    policy: pull
+    <<: *yarn-cache
+
+.junit-artifacts: &junit-artifacts
+  artifacts:
+    expose_as: junit
+    expire_in: 6 mos
+    paths:
+      - junit.xml
+    reports:
+      junit: junit.xml
+
+.with-composer-and-yarn: &with-composer-and-yarn
+  needs:
+    - '📦️ Composer'
+    - '📦️ Yarn'
+  dependencies:
+    - '📦️ Yarn'
+    - '📦️ Composer'
+
+.test-variables: &test-variables
+  FF_NETWORK_PER_BUILD: 1
+  SIMPLETEST_BASE_URL: http://localhost/subdirectory
+  DB_DRIVER: mysql
+  MYSQL_ROOT_PASSWORD: root
+  MYSQL_DATABASE: mysql
+  MYSQL_USER: drupaltestbot
+  MYSQL_PASSWORD: drupaltestbotpw
+  POSTGRES_DB: drupaltestbot
+  POSTGRES_USER: drupaltestbot
+  POSTGRES_PASSWORD: drupaltestbotpw
+  MINK_DRIVER_ARGS_WEBDRIVER: '["chrome", {"browserName":"chrome","chromeOptions":{"args":["--disable-dev-shm-usage","--disable-gpu","--headless"]}}, "http://localhost:9515"]'
+  CI_PARALLEL_NODE_INDEX: $CI_NODE_INDEX
+  CI_PARALLEL_NODE_TOTAL: $CI_NODE_TOTAL
+
+.with-database: &with-database
+  name: $_CONFIG_DOCKERHUB_ROOT/$_TARGET_DB:production
+  alias: database
+
+.with-chrome: &with-chrome
+  name: $_CONFIG_DOCKERHUB_ROOT/chromedriver:production
+  alias: chrome
+  entrypoint:
+    - chromedriver
+    - "--no-sandbox"
+    - "--log-path=/tmp/chromedriver.log"
+    - "--verbose"
+    - "--whitelisted-ips="
+
+.phpunit-artifacts: &phpunit-artifacts
+  artifacts:
+    when: always
+    expire_in: 6 mos
+    reports:
+      junit: ./sites/default/files/simpletest/phpunit-*.xml
+    paths:
+      - ./sites/default/files/simpletest/phpunit-*.xml
+      - ./sites/simpletest/browser_output
+
+.setup-webroot: &setup-webserver
+  before_script:
+    - ln -s $CI_PROJECT_DIR /var/www/html/subdirectory
+    - sudo service apache2 start
+
+.run-tests: &run-tests
+  script:
+    # Determine DB driver.
+    - |
+      [[ $_TARGET_DB == sqlite* ]] && export SIMPLETEST_DB=sqlite://localhost/$CI_PROJECT_DIR/sites/default/files/db.sqlite?module=sqlite
+      [[ $_TARGET_DB == mysql* ]] && export SIMPLETEST_DB=mysql://$MYSQL_USER:$MYSQL_PASSWORD@database/$MYSQL_DATABASE?module=mysql
+      [[ $_TARGET_DB == mariadb* ]] && export SIMPLETEST_DB=mysql://$MYSQL_USER:$MYSQL_PASSWORD@database/$MYSQL_DATABASE?module=mysql
+      [[ $_TARGET_DB == pgsql* ]] && export SIMPLETEST_DB=pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@database/$POSTGRES_DB?module=pgsql
+    - mkdir -p ./sites/simpletest ./sites/default/files ./build/logs/junit /var/www/.composer
+    - chown -R www-data:www-data ./sites ./build/logs/junit ./vendor /var/www/
+    - sudo -u www-data git config --global --add safe.directory $CI_PROJECT_DIR
+    # Need to pass this along directly.
+    - sudo MINK_DRIVER_ARGS_WEBDRIVER="$MINK_DRIVER_ARGS_WEBDRIVER" -u www-data php ./core/scripts/run-tests.sh --color --keep-results --types "$TESTSUITE" --concurrency "$CONCURRENCY" --repeat "1" --sqlite "./sites/default/files/tests.sqlite" --dburl $SIMPLETEST_DB --url $SIMPLETEST_BASE_URL --verbose --non-html --all --ci-parallel-node-index $CI_PARALLEL_NODE_INDEX --ci-parallel-node-total $CI_PARALLEL_NODE_TOTAL
+
+################
+# Jobs
+#
+# Jobs define what scripts are actually executed in each stage.
+#
+# The 'rules' keyword can also be used to define conditions for each job.
+#
+# Documentation: https://docs.gitlab.com/ee/ci/jobs/
+################
+
+################
+# Build Jobs
+################
+
+'📦️ Composer':
+  <<: *default-job-settings
+  stage: 🏗️ Build
+  cache:
+    <<: *composer-cache
+  artifacts:
+    expire_in: 1 week
+    expose_as: 'web-vendor'
+    paths:
+      - vendor/
+  script:
+      - composer validate
+      - composer install
+
+'📦️ Yarn':
+  <<: *default-job-settings
+  stage: 🏗️ Build
+  cache:
+    <<: *yarn-cache
+  artifacts:
+    expire_in: 1 week
+    expose_as: 'yarn-vendor'
+    paths:
+      - core/node_modules/
+  script:
+    # Installs all core javascript dependencies and adds junit formatter.
+    - yarn --cwd ./core add stylelint-junit-formatter
+
+################
+# Test Jobs
+################
+
+'🌐️️ PHPUnit Functional':
+  <<: [ *with-composer-cache, *phpunit-artifacts, *setup-webserver, *run-tests, *default-job-settings ]
+  stage: 🗜️ Test
+  parallel: 7
+  variables:
+    <<: *test-variables
+    TESTSUITE: PHPUnit-Functional
+    CONCURRENCY: "$CONCURRENCY"
+  services:
+    - <<: *with-database
+
+'🩹 Test-only changes':
+  <<: [ *with-composer-cache, *phpunit-artifacts, *setup-webserver, *default-job-settings ]
+  stage: 🗜️ Test
+  when: manual
+  interruptible: true
+  allow_failure: true
+  variables:
+    <<: *test-variables
+  services:
+    - <<: *with-database
+    - <<: *with-chrome
+  script:
+    #  Determine DB driver.
+    - |
+      [[ $_TARGET_DB_TYPE == "sqlite" ]] && export SIMPLETEST_DB=sqlite://localhost/subdirectory/sites/default/files/db.sqlite?module=sqlite
+      [[ $_TARGET_DB_TYPE == "mysql" ]] && export SIMPLETEST_DB=mysql://$MYSQL_USER:$MYSQL_PASSWORD@database/$MYSQL_DATABASE?module=mysql
+      [[ $_TARGET_DB_TYPE == "mariadb" ]] && export SIMPLETEST_DB=mysql://$MYSQL_USER:$MYSQL_PASSWORD@database/$MYSQL_DATABASE?module=mysql
+      [[ $_TARGET_DB_TYPE == "pgsql" ]] && export SIMPLETEST_DB=pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@database/$POSTGRES_DB?module=pgsql
+    - mkdir -p ./sites/simpletest ./sites/default/files ./build/logs/junit /var/www/.composer
+    - chown -R www-data:www-data ./sites ./build/logs/junit ./vendor /var/www/
+    - sudo -u www-data git config --global --add safe.directory $CI_PROJECT_DIR
+    - export TARGET_BRANCH=${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}${CI_COMMIT_BRANCH}
+    - git fetch -vn --depth=3 "$CI_MERGE_REQUEST_PROJECT_URL" "+refs/heads/$TARGET_BRANCH:refs/heads/$TARGET_BRANCH"
+    - |
+      echo "ℹ️ Changes from ${TARGET_BRANCH}"
+      git diff refs/heads/${TARGET_BRANCH} --name-only
+      echo "If this list contains more files than what you changed, then you need to rebase your branch."
+      echo "1️⃣ Reverting non test changes"
+      if [[ $(git diff refs/heads/${TARGET_BRANCH} --diff-filter=DM --name-only|grep -Ev "Test.php$"|grep -v .gitlab-ci|grep -v scripts/run-tests.sh) ]]; then
+        git diff refs/heads/${TARGET_BRANCH} --diff-filter=DM --name-only|grep -Ev "Test.php$"|grep -v .gitlab-ci|grep -v scripts/run-tests.sh|while read file;do
+          echo "↩️ Reverting $file";
+          git checkout refs/heads/${TARGET_BRANCH} -- $file;
+        done
+      fi
+      if [[ $(git diff refs/heads/${TARGET_BRANCH} --diff-filter=A --name-only|grep -Ev "Test.php$"|grep -v .gitlab-ci|grep -v scripts/run-tests.sh) ]]; then
+        git diff refs/heads/${TARGET_BRANCH} --diff-filter=A --name-only|grep -Ev "Test.php$"|grep -v .gitlab-ci|grep -v scripts/run-tests.sh|while read file;do
+          echo "🗑️️ Deleting $file";
+          git rm $file;
+        done
+      fi
+      echo "2️⃣ Running test changes for this branch"
+      if [[ $(git diff refs/heads/${TARGET_BRANCH} --name-only|grep -E "Test.php$") ]]; then
+        for test in `git diff refs/heads/${TARGET_BRANCH} --name-only|grep -E "Test.php$"`; do
+          sudo SIMPLETEST_BASE_URL="$SIMPLETEST_BASE_URL" SIMPLETEST_DB="$SIMPLETEST_DB" MINK_DRIVER_ARGS_WEBDRIVER="$MINK_DRIVER_ARGS_WEBDRIVER" -u www-data ./vendor/bin/phpunit -c core $test --log-junit=./sites/default/files/simpletest/phpunit-`echo $test|sed 's/\//_/g' `.xml;
+        done;
+      fi
+
+'⚙️️ PHPUnit Kernel':
+  <<: [*with-composer-cache, *phpunit-artifacts, *setup-webserver, *run-tests, *default-job-settings ]
+  stage: 🗜️ Test
+  parallel: 2
+  variables:
+    <<: *test-variables
+    TESTSUITE: PHPUnit-Kernel
+    CONCURRENCY: "$CONCURRENCY"
+  services:
+    - <<: *with-database
+
+'🖱️️️ PHPUnit Functional Javascript':
+  <<: [ *with-composer-cache, *phpunit-artifacts, *setup-webserver, *run-tests, *default-job-settings ]
+  stage: 🗜️ Test
+  parallel: 2
+  variables:
+    <<: *test-variables
+    TESTSUITE: PHPUnit-FunctionalJavascript
+    CONCURRENCY: 15
+  services:
+    - <<: *with-database
+    - <<: *with-chrome
+
+'👷️️️ PHPUnit Build':
+  <<: [ *with-composer-cache, *phpunit-artifacts, *setup-webserver, *run-tests, *default-job-settings ]
+  stage: 🗜️ Test
+  variables:
+    <<: *test-variables
+    TESTSUITE: PHPUnit-Build
+    CONCURRENCY: "$CONCURRENCY"
+  services:
+    - <<: *with-database
+
+'⚡️ PHPUnit Unit':
+  <<: [ *with-composer-cache, *phpunit-artifacts, *setup-webserver, *run-tests, *default-job-settings ]
+  stage: 🗜️ Test
+  services:
+    # There are some unit tests that need a database.
+    # @todo Remove after https://www.drupal.org/project/drupal/issues/3386217
+    - <<: *with-database
+  variables:
+    <<: *test-variables
+    TESTSUITE: PHPUnit-Unit
+    CONCURRENCY: "$CONCURRENCY"
+
+'🦉️️️ Nightwatch':
+  <<: [ *with-composer-and-yarn, *setup-webserver, *default-job-settings ]
+  stage: 🗜️ Test
+  variables:
+    <<: *test-variables
+  services:
+    - <<: *with-database
+    - <<: *with-chrome
+  script:
+    # Determine DB driver.
+    - |
+      [[ $_TARGET_DB == sqlite* ]] && export DRUPAL_TEST_DB_URL=sqlite://localhost/$CI_PROJECT_DIR/sites/default/files/db.sqlite?module=sqlite
+      [[ $_TARGET_DB == mysql* ]] && export DRUPAL_TEST_DB_URL=mysql://$MYSQL_USER:$MYSQL_PASSWORD@database/$MYSQL_DATABASE?module=mysql
+      [[ $_TARGET_DB == mariadb* ]] && export DRUPAL_TEST_DB_URL=mysql://$MYSQL_USER:$MYSQL_PASSWORD@database/$MYSQL_DATABASE?module=mysql
+      [[ $_TARGET_DB == pgsql* ]] && export DRUPAL_TEST_DB_URL=pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@database/$POSTGRES_DB?module=pgsql
+    - cp ./core/.env.example ./core/.env
+    # dotenv-safe/config does not support environment variables
+    # @see https://github.com/rolodato/dotenv-safe/issues/126
+    # @todo move this to `variables` when the above is resolved
+    - echo "DRUPAL_TEST_BASE_URL='http://localhost/subdirectory'" >> ./core/.env
+    - echo "DRUPAL_TEST_CHROMEDRIVER_AUTOSTART=false" >> ./core/.env
+    - echo "DRUPAL_TEST_DB_URL='${DRUPAL_TEST_DB_URL}'" >> ./core/.env
+    - echo "DRUPAL_TEST_WEBDRIVER_HOSTNAME='localhost'" >> ./core/.env
+    - echo "DRUPAL_TEST_WEBDRIVER_CHROME_ARGS='--disable-dev-shm-usage --disable-gpu --headless'" >> ./core/.env
+    - echo "DRUPAL_TEST_WEBDRIVER_PORT='9515'" >> ./core/.env
+    - echo "DRUPAL_NIGHTWATCH_OUTPUT='"nightwatch_output"'" >> ./core/.env
+    - cat ./core/.env
+    - mkdir -p ./sites/simpletest ./sites/default/files /var/www/.cache/yarn /var/www/.yarn ./nightwatch_output
+    - chown -R www-data:www-data ./sites/simpletest ./sites/default/files /var/www/.cache/yarn /var/www/.yarn ./nightwatch_output ./core/.env
+    - sudo BABEL_DISABLE_CACHE=1 -u www-data yarn --cwd ./core test:nightwatch --workers=4
+  artifacts:
+    when: always
+    expire_in: 6 mos
+    reports:
+      junit: ./nightwatch_output/*.xml
+    paths:
+      - ./nightwatch_output
diff --git a/composer.json b/composer.json
index f5d4ade87a6bf0edbae601604094713f427659b3..7def5c49476088b21fe6ec906da668d5854415c4 100644
--- a/composer.json
+++ b/composer.json
@@ -24,6 +24,7 @@
         "instaclick/php-webdriver": "^1.4.1",
         "justinrainbow/json-schema": "^5.2",
         "mglaman/phpstan-drupal": "^1.1.37",
+        "micheh/phpcs-gitlab": "^1.1",
         "mikey179/vfsstream": "^1.6.11",
         "open-telemetry/exporter-otlp": "@beta",
         "open-telemetry/sdk": "@beta",
@@ -68,9 +69,6 @@
         }
     },
     "extra": {
-        "branch-alias": {
-          "dev-11.x": "10.2.x-dev"
-        },
         "_readme": [
             "By default Drupal loads the autoloader from ./vendor/autoload.php.",
             "To change the autoloader you can edit ./autoload.php.",
diff --git a/composer.lock b/composer.lock
index 6624857bcc629131ed3cb36b1b5b7a8c276bfd06..496a5111901cc8f1bf43c277ce8e7bf69dc32eba 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "40db4ae3b9b1104696cd4eed364cfec7",
+    "content-hash": "34218586d14cb8c4f987aeec01c2432b",
     "packages": [
         {
             "name": "asm89/stack-cors",
@@ -495,7 +495,7 @@
             "dist": {
                 "type": "path",
                 "url": "core",
-                "reference": "4c5eafca7cdc5f796a213a6722415c7f371731ef"
+                "reference": "d8c2e380c13d2892038560c5591ef07ac31c6add"
             },
             "require": {
                 "asm89/stack-cors": "^2.1",
@@ -573,9 +573,6 @@
             },
             "type": "drupal-core",
             "extra": {
-                "branch-alias": {
-                    "dev-11.x": "10.2.x-dev"
-                },
                 "drupal-scaffold": {
                     "file-mapping": {
                         "[project-root]/.editorconfig": "assets/scaffold/files/editorconfig",
diff --git a/composer/Metapackage/CoreRecommended/composer.json b/composer/Metapackage/CoreRecommended/composer.json
index 901b6565a084948d4d74f6f7aebd29b9a0ab7831..6e33b9eeeb7d1c4095ef03e5e5104f9d508c28af 100644
--- a/composer/Metapackage/CoreRecommended/composer.json
+++ b/composer/Metapackage/CoreRecommended/composer.json
@@ -7,7 +7,7 @@
         "webflo/drupal-core-strict": "*"
     },
     "require": {
-        "drupal/core": "10.2.x-dev",
+        "drupal/core": "11.x-dev",
         "asm89/stack-cors": "~v2.1.1",
         "composer/semver": "~3.3.2",
         "doctrine/annotations": "~1.14.3",
diff --git a/composer/Metapackage/DevDependencies/composer.json b/composer/Metapackage/DevDependencies/composer.json
index f594b5cf46fc7cf1e3eef67c5d896d184f78674e..a3122d3f071e45e01079aa48e788e1d5bff65f25 100644
--- a/composer/Metapackage/DevDependencies/composer.json
+++ b/composer/Metapackage/DevDependencies/composer.json
@@ -16,6 +16,7 @@
         "instaclick/php-webdriver": "^1.4.1",
         "justinrainbow/json-schema": "^5.2",
         "mglaman/phpstan-drupal": "^1.1.37",
+        "micheh/phpcs-gitlab": "^1.1",
         "mikey179/vfsstream": "^1.6.11",
         "open-telemetry/exporter-otlp": "@beta",
         "open-telemetry/sdk": "@beta",
diff --git a/composer/Metapackage/PinnedDevDependencies/composer.json b/composer/Metapackage/PinnedDevDependencies/composer.json
index 005b5060679e8f3a09bef01f46a43ebb150365d0..3cc50f180ae10ab454de4a99be33a024166fbbab 100644
--- a/composer/Metapackage/PinnedDevDependencies/composer.json
+++ b/composer/Metapackage/PinnedDevDependencies/composer.json
@@ -7,7 +7,7 @@
         "webflo/drupal-core-require-dev": "*"
     },
     "require": {
-        "drupal/core": "10.2.x-dev",
+        "drupal/core": "11.x-dev",
         "behat/mink": "v1.10.0",
         "behat/mink-browserkit-driver": "v2.1.0",
         "behat/mink-selenium2-driver": "v1.6.0",
diff --git a/composer/Plugin/Scaffold/ComposerScaffoldCommand.php b/composer/Plugin/Scaffold/ComposerScaffoldCommand.php
index c1b34ca503b6459751c8b7e303073a3f9d4e8af5..13a6aa7d32bb6df4a39d4c054370a294f16ef924 100644
--- a/composer/Plugin/Scaffold/ComposerScaffoldCommand.php
+++ b/composer/Plugin/Scaffold/ComposerScaffoldCommand.php
@@ -48,7 +48,7 @@ protected function configure() {
    * {@inheritdoc}
    */
   protected function execute(InputInterface $input, OutputInterface $output): int {
-    $handler = new Handler($this->getComposer(), $this->getIO());
+    $handler = new Handler($this->requireComposer(), $this->getIO());
     $handler->scaffold();
     return 0;
   }
diff --git a/composer/Template/LegacyProject/composer.json b/composer/Template/LegacyProject/composer.json
index a8fceccd0dd25590ff8cd74889af97fb1b6d142b..204d1dcba1eb6ec152f3efdd116de2e9f579bdc8 100644
--- a/composer/Template/LegacyProject/composer.json
+++ b/composer/Template/LegacyProject/composer.json
@@ -16,13 +16,13 @@
     ],
     "require": {
         "composer/installers": "^2.0",
-        "drupal/core-composer-scaffold": "^10.2",
-        "drupal/core-project-message": "^10.2",
-        "drupal/core-recommended": "^10.2",
-        "drupal/core-vendor-hardening": "^10.2"
+        "drupal/core-composer-scaffold": "^11",
+        "drupal/core-project-message": "^11",
+        "drupal/core-recommended": "^11",
+        "drupal/core-vendor-hardening": "^11"
     },
     "require-dev": {
-        "drupal/core-dev": "^10.2"
+        "drupal/core-dev": "^11"
     },
     "conflict": {
         "drupal/drupal": "*"
diff --git a/composer/Template/RecommendedProject/composer.json b/composer/Template/RecommendedProject/composer.json
index 56d98de870fd8497c08158fe5339ac3cb470288b..0b4926dabfda9080ceb2d992ffbaca187398ff0d 100644
--- a/composer/Template/RecommendedProject/composer.json
+++ b/composer/Template/RecommendedProject/composer.json
@@ -16,12 +16,12 @@
     ],
     "require": {
         "composer/installers": "^2.0",
-        "drupal/core-composer-scaffold": "^10.2",
-        "drupal/core-project-message": "^10.2",
-        "drupal/core-recommended": "^10.2"
+        "drupal/core-composer-scaffold": "^11",
+        "drupal/core-project-message": "^11",
+        "drupal/core-recommended": "^11"
     },
     "require-dev": {
-        "drupal/core-dev": "^10.2"
+        "drupal/core-dev": "^11"
     },
     "conflict": {
         "drupal/drupal": "*"
diff --git a/core/.deprecation-ignore.txt b/core/.deprecation-ignore.txt
index 9e41b3776efce713122211a33a620996a82a9723..e95032cf1d715dcbd2a87399484d97d71619e8bf 100644
--- a/core/.deprecation-ignore.txt
+++ b/core/.deprecation-ignore.txt
@@ -5,11 +5,22 @@
 %The "Symfony\\Component\\Validator\\Context\\ExecutionContextInterface::.*\(\)" method is considered internal Used by the validator engine\. (Should not be called by user\W+code\. )?It may change without further notice\. You should not extend it from "[^"]+"\.%
 %The "PHPUnit\\Framework\\TestCase::addWarning\(\)" method is considered internal%
 
-# Skip non-Symfony DebugClassLoader forward compatibility warnings.
-%Method "(?!Symfony\\)[^"]+" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
-
-# Skip DebugClassLoader false positives.
-%Method "[^"]+" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "(?!Drupal\\)[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+# Skip some dependencies' DebugClassLoader forward compatibility warnings.
+%Method "Behat\\[^"]+" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Composer\\EventDispatcher\\EventSubscriberInterface::getSubscribedEvents\(\)" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Composer\\Plugin\\PluginInterface::(activate|deactivate|uninstall)\(\)" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Doctrine\\Common\\Annotations\\Reader::[^"]+" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Twig\\Extension\\ExtensionInterface::[^"]+" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Twig\\Loader\\FilesystemLoader::findTemplate\(\)" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Twig\\Loader\\LoaderInterface::exists\(\)" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Twig\\Node\\Node::compile\(\)" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Twig\\NodeVisitor\\AbstractNodeVisitor::[^"]+" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Twig\\NodeVisitor\\NodeVisitorInterface::[^"]+" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+%Method "Twig\\TokenParser\\TokenParserInterface::[^"]+" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
+
+# Skip root namespace native DebugClassLoader forward compatibility warnings.
+# These mostly refer to PHP native classes, could be fixed for PHP 8.1.
+%Method "[^\\]+::\w+\(\)" might add "[^"]+" as a native return type declaration in the future. Do the same in (child class|implementation) "[^"]+" now to avoid errors or add an explicit @return annotation to suppress this message%
 
 # The following deprecation is listed for Twig 2 compatibility when unit
 # testing using \Symfony\Component\ErrorHandler\DebugClassLoader.
@@ -33,9 +44,9 @@
 
 # Skip deprecations for EditorMediaDialog, EditorImageDialog and EditorLinkDialog triggered by
 # \Drupal\Core\Entity\EntityResolverManager::setRouteOptions().
-%Drupal\\media\\Form\\EditorMediaDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493%
-%Drupal\\editor\\Form\\EditorImageDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493%
-%Drupal\\editor\\Form\\EditorLinkDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493%
+%Drupal\\media\\Form\\EditorMediaDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493%
+%Drupal\\editor\\Form\\EditorImageDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493%
+%Drupal\\editor\\Form\\EditorLinkDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493%
 
 # Symfony 7.
 %Method "Symfony\\Contracts\\Service\\ResetInterface::reset\(\)" might add "void" as a native return type declaration in the future. Do the same in implementation "Drupal\\Component\\DependencyInjection\\Container" now to avoid errors or add an explicit @return annotation to suppress this message.%
diff --git a/core/.eslintrc.json b/core/.eslintrc.json
index a2faf64523304c5b39ef8da0da82ad16a8e37bb1..d3ae96528986eaca640f95484827e85967f73a5c 100644
--- a/core/.eslintrc.json
+++ b/core/.eslintrc.json
@@ -10,6 +10,9 @@
     "es6": true,
     "node": true
   },
+  "parserOptions": {
+    "ecmaVersion": 2020
+  },
   "globals": {
     "Drupal": true,
     "drupalSettings": true,
diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index 518268c0ba352e99860691a6c6b4a2789e5c4583..a32c745e669ff1a0e3708f5dfe1c893843f115fe 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -43,12 +43,15 @@ Release managers
 - (provisional) Dave Long 'longwave' https://www.drupal.org/u/longwave
 
 Core JavaScript packages
- - Théodore Biadala 'nod_' https://www.drupal.org/u/nod_
- - Sally Young 'justafish' https://www.drupal.org/u/justafish
+- Théodore Biadala 'nod_' https://www.drupal.org/u/nod_
+- Sally Young 'justafish' https://www.drupal.org/u/justafish
 
 Committer team facilitators
 - Pamela Barone 'pameeela' https://www.drupal.org/u/pameeela
 
+Core initiative facilitators
+- Gábor Hojtsy 'Gábor Hojtsy' https://www.drupal.org/u/gábor-hojtsy
+
 Subsystem maintainers
 ---------------------
 
diff --git a/core/composer.json b/core/composer.json
index c38277c62f358c945e5b3678c42898fdfd3e0006..47cf2763b385e3ed9099aeb4c800ea630bf50ca4 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -115,9 +115,6 @@
         "ext-zip": "Needed to extend the plugin.manager.archiver service capability with the handling of files in the ZIP format."
     },
     "extra": {
-        "branch-alias": {
-          "dev-11.x": "10.2.x-dev"
-        },
         "drupal-scaffold": {
             "file-mapping": {
                 "[project-root]/.editorconfig": "assets/scaffold/files/editorconfig",
diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index 56cc1506b4128eccc3d00578b2429c3da8edaf4b..8935e21ef151e9f8208f8cb13d4556958fd5d675 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -25,6 +25,9 @@ boolean:
 email:
   label: 'Email'
   class: '\Drupal\Core\TypedData\Plugin\DataType\Email'
+  constraints:
+    Email:
+      message: "%value is not a valid email address."
 integer:
   label: 'Integer'
   class: '\Drupal\Core\TypedData\Plugin\DataType\IntegerData'
diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index 493ae2dedf65299c754e25c52faa7c141f870bd8..c88ca10bd3d9b9ca90b3c30931794e86b74a643d 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -233,6 +233,14 @@ field.widget.settings.checkbox:
       type: boolean
       label: 'Use field label instead of the "On value" as label'
 
+field.widget.settings.language_select:
+  type: mapping
+  label: 'Language format settings'
+  mapping:
+    include_locked:
+      type: boolean
+      label: 'Include locked languages'
+
 field.widget.settings.entity_reference_autocomplete_tags:
   type: mapping
   label: 'Entity reference autocomplete (Tags style) display format settings'
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 9f17f189f50d8f98700648ecd07428dd2442b67b..7df34ed0ea2c663f93410ba5085b5c32146a493d 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -405,42 +405,54 @@ loadjs:
   js:
     assets/vendor/loadjs/loadjs.min.js: { minified: true }
 
-drupal.autocomplete:
+# Common files for libraries that still rely on jQuery UI components.
+# @todo Remove when no longer required by drupal.autocomplete and drupal.dialog.
+internal.jquery_ui:
   version: VERSION
   js:
-    misc/autocomplete.js: { weight: -1 }
-    # The remaining JavaScript assets previously came from core/jquery.ui, a
-    # deprecated library.
     # All weights are based on the requirements defined within each file.
-    # @todo replace with solution in https://drupal.org/node/3076171
-    assets/vendor/jquery.ui/ui/widgets/autocomplete-min.js: { weight: -11.7, minified: true }
     assets/vendor/jquery.ui/ui/labels-min.js: { weight: -11.7, minified: true }
-    assets/vendor/jquery.ui/ui/widgets/menu-min.js: { weight: -11.7, minified: true }
     assets/vendor/jquery.ui/ui/data-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/disable-selection-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/focusable-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/form-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/ie-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/jquery-patch-min.js: { weight: -11.8, minified: true }
+    assets/vendor/jquery.ui/ui/scroll-parent-min.js: { weight: -11.8, minified: true }
+    assets/vendor/jquery.ui/ui/unique-id-min.js: { weight: -11.8, minified: true }
+    assets/vendor/jquery.ui/ui/focusable-min.js: { weight: -11.8, minified: true }
+    assets/vendor/jquery.ui/ui/ie-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/keycode-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/plugin-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/safe-active-element-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/safe-blur-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/scroll-parent-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/unique-id-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/widget-min.js: { weight: -11.8, minified: true }
     assets/vendor/jquery.ui/ui/version-min.js: { weight: -11.9, minified: true }
+  css:
+    component:
+      assets/vendor/jquery.ui/themes/base/core.css: { weight: -11.8 }
+    theme:
+      assets/vendor/jquery.ui/themes/base/theme.css: { weight: -11.8 }
+  dependencies:
+    - core/jquery
+
+drupal.autocomplete:
+  version: VERSION
+  js:
+    misc/autocomplete.js: { weight: -1 }
+    # The remaining JavaScript assets previously came from core/jquery.ui, a
+    # deprecated library.
+    # All weights are based on the requirements defined within each file.
+    # @todo replace with solution in https://drupal.org/node/3076171
+    assets/vendor/jquery.ui/ui/widgets/autocomplete-min.js: { weight: -11.7, minified: true }
+    assets/vendor/jquery.ui/ui/widgets/menu-min.js: { weight: -11.7, minified: true }
   # All CSS assets previously came from core/jquery.ui, a deprecated library.
   # @todo replace with solution found in https://drupal.org/node/3076171
   css:
     component:
       assets/vendor/jquery.ui/themes/base/autocomplete.css: { weight: -11.7 }
       assets/vendor/jquery.ui/themes/base/menu.css: { weight: -11.7 }
-      assets/vendor/jquery.ui/themes/base/core.css: { weight: -11.8 }
-    theme:
-      assets/vendor/jquery.ui/themes/base/theme.css: { weight: -11.8 }
   dependencies:
     - core/jquery
+    - core/internal.jquery_ui
     - core/drupal
     - core/drupalSettings
     - core/drupal.ajax
@@ -518,22 +530,7 @@ drupal.dialog:
     assets/vendor/jquery.ui/ui/widgets/resizable-min.js: { weight: -11.6, minified: true }
     assets/vendor/jquery.ui/ui/widgets/controlgroup-min.js: { weight: -11.7, minified: true }
     assets/vendor/jquery.ui/ui/form-reset-mixin-min.js: { weight: -11.7, minified: true }
-    assets/vendor/jquery.ui/ui/labels-min.js: { weight: -11.7, minified: true }
     assets/vendor/jquery.ui/ui/widgets/mouse-min.js: { weight: -11.7, minified: true }
-    assets/vendor/jquery.ui/ui/data-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/disable-selection-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/form-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/jquery-patch-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/scroll-parent-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/unique-id-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/focusable-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/ie-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/keycode-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/plugin-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/safe-active-element-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/safe-blur-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/widget-min.js: { weight: -11.8, minified: true }
-    assets/vendor/jquery.ui/ui/version-min.js: { weight: -11.9, minified: true }
     assets/vendor/tua-body-scroll-lock/tua-bsl.umd.min.js: { weight: -1,  minified: true }
   # All CSS assets previously came from core/jquery.ui, a deprecated library.
   # @todo replace with solution found in https://drupal.org/node/2158943
@@ -544,11 +541,9 @@ drupal.dialog:
       assets/vendor/jquery.ui/themes/base/checkboxradio.css: { weight: -11.6 }
       assets/vendor/jquery.ui/themes/base/resizable.css: { weight: -11.6 }
       assets/vendor/jquery.ui/themes/base/controlgroup.css: { weight: -11.7 }
-      assets/vendor/jquery.ui/themes/base/core.css: { weight: -11.8 }
-    theme:
-      assets/vendor/jquery.ui/themes/base/theme.css: { weight: -11.8 }
   dependencies:
     - core/jquery
+    - core/internal.jquery_ui
     - core/drupal
     - core/drupalSettings
     - core/drupal.debounce
diff --git a/core/core.services.yml b/core/core.services.yml
index 32e39e055eaa01eeaa04ffd761a26c6b6af09305..0b093c466998e379f41fba837ce6cafa2302d5e1 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1613,6 +1613,7 @@ services:
     arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack', '@file_system', '@config.factory', '@file_url_generator', '@datetime.time', '@language_manager']
   asset.js.optimizer:
     class: Drupal\Core\Asset\JsOptimizer
+    arguments: ['@logger.channel.default']
   asset.js.collection_grouper:
     class: Drupal\Core\Asset\JsCollectionGrouper
   asset.js.dumper:
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 9d1f0781306eb4b1b1aacac5e4c9d27eb7118866..cabd3ceeb8f410bf63cda91f2e7746af87cba381 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -8,11 +8,10 @@
  * a cached page are instead located in bootstrap.inc.
  */
 
-use Drupal\Component\Utility\Bytes;
 use Drupal\Component\Utility\SortArray;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\DrupalKernel;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 
 /**
  * @defgroup php_wrappers PHP wrapper functions
@@ -132,48 +131,16 @@
  *
  * @return \Drupal\Core\StringTranslation\TranslatableMarkup
  *   A translated string representation of the size.
+ *
+ * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+ *   \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode)
+ *   instead.
+ *
+ * @see https://www.drupal.org/node/2999981
  */
 function format_size($size, $langcode = NULL) {
-  $absolute_size = abs($size);
-  if ($absolute_size < Bytes::KILOBYTE) {
-    return \Drupal::translation()->formatPlural($size, '1 byte', '@count bytes', [], ['langcode' => $langcode]);
-  }
-  // Create a multiplier to preserve the sign of $size.
-  $sign = $absolute_size / $size;
-  foreach (['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] as $unit) {
-    $absolute_size /= Bytes::KILOBYTE;
-    $rounded_size = round($absolute_size, 2);
-    if ($rounded_size < Bytes::KILOBYTE) {
-      break;
-    }
-  }
-  $args = ['@size' => $rounded_size * $sign];
-  $options = ['langcode' => $langcode];
-  switch ($unit) {
-    case 'KB':
-      return new TranslatableMarkup('@size KB', $args, $options);
-
-    case 'MB':
-      return new TranslatableMarkup('@size MB', $args, $options);
-
-    case 'GB':
-      return new TranslatableMarkup('@size GB', $args, $options);
-
-    case 'TB':
-      return new TranslatableMarkup('@size TB', $args, $options);
-
-    case 'PB':
-      return new TranslatableMarkup('@size PB', $args, $options);
-
-    case 'EB':
-      return new TranslatableMarkup('@size EB', $args, $options);
-
-    case 'ZB':
-      return new TranslatableMarkup('@size ZB', $args, $options);
-
-    case 'YB':
-      return new TranslatableMarkup('@size YB', $args, $options);
-  }
+  @trigger_error('format_size() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode) instead. See https://www.drupal.org/node/2999981', E_USER_DEPRECATED);
+  return ByteSizeMarkup::create($size ?? 0, $langcode);
 }
 
 /**
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 2692d6bed1d07a985f267b9987aea0363cbda6c3..43b96e1e08eba4906a44607a9314f6e482f27423 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -9,7 +9,6 @@
  */
 
 use Drupal\Component\Render\FormattableMarkup;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Url;
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Crypt;
@@ -1837,7 +1836,7 @@ function template_preprocess_pager(&$variables) {
     $items['pages'][$i]['attributes'] = new Attribute();
     if ($i == $pager_current) {
       $variables['current'] = $i;
-      $items['pages'][$i]['attributes']->setAttribute('aria-current', new TranslatableMarkup('Current page'));
+      $items['pages'][$i]['attributes']->setAttribute('aria-current', 'page');
     }
   }
   // Add an ellipsis if there are further next pages.
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 33b94192a2b4b6ce29fc4092aaff035e844fc989..cb51a509fcee11f38c18047f3a5b20707ab5d1e3 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -75,7 +75,7 @@ class Drupal {
   /**
    * The current system version.
    */
-  const VERSION = '10.2.0-dev';
+  const VERSION = '11.0-dev';
 
   /**
    * Core API compatibility.
diff --git a/core/lib/Drupal/Component/Annotation/composer.json b/core/lib/Drupal/Component/Annotation/composer.json
index a755e8f048675147af091601089c74e521f38ecf..feabe9279fe0817c209302935537cd5afa3f8898 100644
--- a/core/lib/Drupal/Component/Annotation/composer.json
+++ b/core/lib/Drupal/Component/Annotation/composer.json
@@ -9,9 +9,9 @@
     "require": {
         "php": ">=8.1.0",
         "doctrine/annotations": "^1.14",
-        "drupal/core-file-cache": "10.2.x-dev",
-        "drupal/core-plugin": "10.2.x-dev",
-        "drupal/core-utility": "10.2.x-dev"
+        "drupal/core-file-cache": "11.x-dev",
+        "drupal/core-plugin": "11.x-dev",
+        "drupal/core-utility": "11.x-dev"
     },
     "autoload": {
         "psr-4": {
diff --git a/core/lib/Drupal/Component/Assertion/Handle.php b/core/lib/Drupal/Component/Assertion/Handle.php
index 7ef456c5058511f85d6f665e57fef5966a9f05d5..fe94bbf9d0590f619f88353c6c40df80170f80a6 100644
--- a/core/lib/Drupal/Component/Assertion/Handle.php
+++ b/core/lib/Drupal/Component/Assertion/Handle.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Component\Assertion;
 
-trigger_error(__NAMESPACE__ . '\Handle is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, use assert_options(ASSERT_EXCEPTION, TRUE). See https://drupal.org/node/3105918', E_USER_DEPRECATED);
+trigger_error(__NAMESPACE__ . '\Handle is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, use assert_options(ASSERT_EXCEPTION, TRUE). See https://www.drupal.org/node/3105918', E_USER_DEPRECATED);
 
 /**
  * Handler for runtime assertion failures.
diff --git a/core/lib/Drupal/Component/Datetime/Time.php b/core/lib/Drupal/Component/Datetime/Time.php
index 566debba0494cacae2002c92a674564c97396748..ec660094fc985c88d5c17948a80783ed38b54fe8 100644
--- a/core/lib/Drupal/Component/Datetime/Time.php
+++ b/core/lib/Drupal/Component/Datetime/Time.php
@@ -6,23 +6,32 @@
 
 /**
  * Provides a class for obtaining system time.
+ *
+ * While the normal use case of this class expects that a Request object is
+ * available from the RequestStack, it is still possible to use it without, for
+ * example for early bootstrap containers or for unit tests. In those cases,
+ * the class will access global variables or set a proxy request time in order
+ * to return the request time.
  */
 class Time implements TimeInterface {
 
   /**
    * The request stack.
-   *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
    */
-  protected $requestStack;
+  protected ?RequestStack $requestStack;
+
+  /**
+   * A proxied request time if the request time is not available.
+   */
+  protected float $proxyRequestTime;
 
   /**
    * Constructs a Time object.
    *
-   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
-   *   The request stack.
+   * @param \Symfony\Component\HttpFoundation\RequestStack|null $request_stack
+   *   (Optional) The request stack.
    */
-  public function __construct(RequestStack $request_stack) {
+  public function __construct(RequestStack $request_stack = NULL) {
     $this->requestStack = $request_stack;
   }
 
@@ -30,26 +39,26 @@ public function __construct(RequestStack $request_stack) {
    * {@inheritdoc}
    */
   public function getRequestTime() {
-    $request = $this->requestStack->getCurrentRequest();
+    $request = $this->requestStack ? $this->requestStack->getCurrentRequest() : NULL;
     if ($request) {
       return $request->server->get('REQUEST_TIME');
     }
     // If this is called prior to the request being pushed to the stack fallback
     // to built-in globals (if available) or the system time.
-    return $_SERVER['REQUEST_TIME'] ?? $this->getCurrentTime();
+    return $_SERVER['REQUEST_TIME'] ?? $this->getProxyRequestTime();
   }
 
   /**
    * {@inheritdoc}
    */
   public function getRequestMicroTime() {
-    $request = $this->requestStack->getCurrentRequest();
+    $request = $this->requestStack ? $this->requestStack->getCurrentRequest() : NULL;
     if ($request) {
       return $request->server->get('REQUEST_TIME_FLOAT');
     }
     // If this is called prior to the request being pushed to the stack fallback
     // to built-in globals (if available) or the system time.
-    return $_SERVER['REQUEST_TIME_FLOAT'] ?? $this->getCurrentMicroTime();
+    return $_SERVER['REQUEST_TIME_FLOAT'] ?? $this->getProxyRequestMicroTime();
   }
 
   /**
@@ -66,4 +75,30 @@ public function getCurrentMicroTime() {
     return microtime(TRUE);
   }
 
+  /**
+   * Returns a mimic of the timestamp of the current request.
+   *
+   * @return int
+   *   A value returned by time().
+   */
+  protected function getProxyRequestTime(): int {
+    if (!isset($this->proxyRequestTime)) {
+      $this->proxyRequestTime = $this->getCurrentMicroTime();
+    }
+    return (int) $this->proxyRequestTime;
+  }
+
+  /**
+   * Returns a mimic of the timestamp of the current request.
+   *
+   * @return float
+   *   A value returned by microtime().
+   */
+  protected function getProxyRequestMicroTime(): float {
+    if (!isset($this->proxyRequestTime)) {
+      $this->proxyRequestTime = $this->getCurrentMicroTime();
+    }
+    return $this->proxyRequestTime;
+  }
+
 }
diff --git a/core/lib/Drupal/Component/Datetime/composer.json b/core/lib/Drupal/Component/Datetime/composer.json
index 7a16ab9e344f7f0cc2dc7141653a47b87f66e325..91bd608f5570c559da95bee80c2aa924234caaa8 100644
--- a/core/lib/Drupal/Component/Datetime/composer.json
+++ b/core/lib/Drupal/Component/Datetime/composer.json
@@ -8,7 +8,7 @@
     "license": "GPL-2.0-or-later",
     "require": {
         "php": ">=8.1.0",
-        "drupal/core-utility": "10.2.x-dev"
+        "drupal/core-utility": "11.x-dev"
     },
     "autoload": {
         "psr-4": {
diff --git a/core/lib/Drupal/Component/Diff/DiffOpOutputBuilder.php b/core/lib/Drupal/Component/Diff/DiffOpOutputBuilder.php
index c872a92f50e8d97a1912960f03beec33c0b9a783..41a850bb5dc1d5e32a7aec013827c142f2f74638 100644
--- a/core/lib/Drupal/Component/Diff/DiffOpOutputBuilder.php
+++ b/core/lib/Drupal/Component/Diff/DiffOpOutputBuilder.php
@@ -61,16 +61,8 @@ public function toOpsArray(array $diff): array {
         if (count($hunkTarget) === 0) {
           $ops[] = $this->hunkOp(Differ::REMOVED, $hunkSource, $hunkTarget);
         }
-        elseif (count($hunkSource) === count($hunkTarget)) {
-          $ops[] = $this->hunkOp(self::CHANGED, $hunkSource, $hunkTarget);
-        }
-        elseif (count($hunkSource) > count($hunkTarget)) {
-          $ops[] = $this->hunkOp(self::CHANGED, array_slice($hunkSource, 0, count($hunkTarget)), $hunkTarget);
-          $ops[] = $this->hunkOp(Differ::REMOVED, array_slice($hunkSource, count($hunkTarget)), []);
-        }
         else {
-          $ops[] = $this->hunkOp(self::CHANGED, $hunkSource, array_slice($hunkTarget, 0, count($hunkSource)));
-          $ops[] = $this->hunkOp(Differ::ADDED, array_slice($hunkTarget, count($hunkSource)), []);
+          $ops[] = $this->hunkOp(self::CHANGED, $hunkSource, $hunkTarget);
         }
         $hunkMode = NULL;
         $hunkSource = [];
diff --git a/core/lib/Drupal/Component/Discovery/composer.json b/core/lib/Drupal/Component/Discovery/composer.json
index c0800a842b7c169009507706838701ccc59f9985..4a9343967682b60fc6e33c276d411a1f80a6c365 100644
--- a/core/lib/Drupal/Component/Discovery/composer.json
+++ b/core/lib/Drupal/Component/Discovery/composer.json
@@ -8,8 +8,8 @@
     "license": "GPL-2.0-or-later",
     "require": {
         "php": ">=8.1.0",
-        "drupal/core-file-cache": "10.2.x-dev",
-        "drupal/core-serialization": "10.2.x-dev"
+        "drupal/core-file-cache": "11.x-dev",
+        "drupal/core-serialization": "11.x-dev"
     },
     "autoload": {
         "psr-4": {
diff --git a/core/lib/Drupal/Component/FrontMatter/composer.json b/core/lib/Drupal/Component/FrontMatter/composer.json
index ac246ae49b1570ae70cf22a6eb881260fae04bea..9baa12c9d92d3c36b822103d0b4858281b873d51 100644
--- a/core/lib/Drupal/Component/FrontMatter/composer.json
+++ b/core/lib/Drupal/Component/FrontMatter/composer.json
@@ -8,7 +8,7 @@
     "license": "GPL-2.0-or-later",
     "require": {
         "php": ">=8.1.0",
-        "drupal/core-serialization": "10.2.x-dev"
+        "drupal/core-serialization": "11.x-dev"
     },
     "autoload": {
         "psr-4": {
diff --git a/core/lib/Drupal/Component/Gettext/composer.json b/core/lib/Drupal/Component/Gettext/composer.json
index 8893295f5fb00a6a3d60cb2c975e9c188020aa4b..0db3a4bc30b6db7ee1c4411bbd75614ab08f5f4d 100644
--- a/core/lib/Drupal/Component/Gettext/composer.json
+++ b/core/lib/Drupal/Component/Gettext/composer.json
@@ -9,7 +9,7 @@
     },
     "require": {
         "php": ">=8.1.0",
-        "drupal/core-render": "10.2.x-dev"
+        "drupal/core-render": "11.x-dev"
     },
     "autoload": {
         "psr-4": {
diff --git a/core/lib/Drupal/Component/PhpStorage/composer.json b/core/lib/Drupal/Component/PhpStorage/composer.json
index 29466cafd95ca577eaa8f4abd069b221e047d930..0517f06b0e35c65a610a043656a3c89ec3130299 100644
--- a/core/lib/Drupal/Component/PhpStorage/composer.json
+++ b/core/lib/Drupal/Component/PhpStorage/composer.json
@@ -8,7 +8,7 @@
     "license": "GPL-2.0-or-later",
     "require": {
         "php": ">=8.1.0",
-        "drupal/core-file-security": "10.2.x-dev"
+        "drupal/core-file-security": "11.x-dev"
     },
     "autoload": {
         "psr-4": {
diff --git a/core/lib/Drupal/Component/Plugin/PluginHelper.php b/core/lib/Drupal/Component/Plugin/PluginHelper.php
index 550a0913430faf6c2ba3e97136a708b97bdd5e57..8cf571c5a986356a53954537da550cf780f42f29 100644
--- a/core/lib/Drupal/Component/Plugin/PluginHelper.php
+++ b/core/lib/Drupal/Component/Plugin/PluginHelper.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Component\Plugin;
 
-@trigger_error('The ' . __NAMESPACE__ . '\PluginHelper is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, use instanceof() to check for \Drupal\Component\Plugin\ConfigurableInterface. See http://drupal.org/node/3198285', E_USER_DEPRECATED);
+@trigger_error('The ' . __NAMESPACE__ . '\PluginHelper is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, use instanceof() to check for \Drupal\Component\Plugin\ConfigurableInterface. See https://www.drupal.org/node/3198285', E_USER_DEPRECATED);
 
 /**
  * A helper class to determine if a plugin is configurable.
diff --git a/core/lib/Drupal/Component/Render/FormattableMarkup.php b/core/lib/Drupal/Component/Render/FormattableMarkup.php
index 937bfc339fdf97316ac79b1679e976b96f67b215..8750a5dcfb2c288b07e4d4e08d0090bc4fb4d979 100644
--- a/core/lib/Drupal/Component/Render/FormattableMarkup.php
+++ b/core/lib/Drupal/Component/Render/FormattableMarkup.php
@@ -201,6 +201,7 @@ protected static function placeholderFormat($string, array $args) {
         // and in D11 this will no longer be allowed. When this trigger_error
         // is removed, also remove isset $value checks inside the switch{}
         // below.
+        // phpcs:ignore Drupal.Semantics.FunctionTriggerError
         @trigger_error(sprintf('Deprecated NULL placeholder value for key (%s) in: "%s". This will throw a PHP error in drupal:11.0.0. See https://www.drupal.org/node/3318826', (string) $key, (string) $string), E_USER_DEPRECATED);
         $value = '';
       }
@@ -241,8 +242,10 @@ protected static function placeholderFormat($string, array $args) {
           break;
 
         default:
-          // Warn for random variables that won't be replaced.
-          trigger_error(sprintf('Invalid placeholder (%s) with string: "%s"', $key, $string), E_USER_WARNING);
+          if (!ctype_alnum($key[0])) {
+            // Warn for random placeholders that won't be replaced.
+            trigger_error(sprintf('Invalid placeholder (%s) with string: "%s"', $key, $string), E_USER_WARNING);
+          }
           // No replacement possible therefore we can discard the argument.
           unset($args[$key]);
           break;
diff --git a/core/lib/Drupal/Component/Render/composer.json b/core/lib/Drupal/Component/Render/composer.json
index fd01afa0b255d2ece199d543df4b0ddbacdda904..338eb5135b2b8798493ed4c551ef5d4a8963d00e 100644
--- a/core/lib/Drupal/Component/Render/composer.json
+++ b/core/lib/Drupal/Component/Render/composer.json
@@ -8,7 +8,7 @@
     "license": "GPL-2.0-or-later",
     "require": {
         "php": ">=8.1.0",
-        "drupal/core-utility": "10.2.x-dev"
+        "drupal/core-utility": "11.x-dev"
     },
     "autoload": {
         "psr-4": {
diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php
index 0a833db37ee98997e070e6e9125781e4d0eb66d5..d946e23ba9aaa92b9a00fdf33c31b07246b0dbf1 100644
--- a/core/lib/Drupal/Component/Utility/Html.php
+++ b/core/lib/Drupal/Component/Utility/Html.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Component\Utility;
 
+use Masterminds\HTML5;
+use Masterminds\HTML5\Serializer\Traverser;
+
 /**
  * Provides DOMDocument helpers for parsing and serializing HTML strings.
  *
@@ -146,7 +149,7 @@ public static function setIsAjax($is_ajax) {
    * This function ensures that each passed HTML ID value only exists once on
    * the page. By tracking the already returned ids, this function enables
    * forms, blocks, and other content to be output multiple times on the same
-   * page, without breaking (X)HTML validation.
+   * page, without breaking HTML validation.
    *
    * For already existing IDs, a counter is appended to the ID string.
    * Therefore, JavaScript and CSS code should not rely on any value that was
@@ -258,49 +261,39 @@ public static function normalize($html) {
   /**
    * Parses an HTML snippet and returns it as a DOM object.
    *
-   * This function loads the body part of a partial (X)HTML document and returns
-   * a full \DOMDocument object that represents this document.
+   * This function loads the body part of a partial HTML document and returns a
+   * full \DOMDocument object that represents this document.
    *
    * Use \Drupal\Component\Utility\Html::serialize() to serialize this
    * \DOMDocument back to a string.
    *
    * @param string $html
-   *   The partial (X)HTML snippet to load. Invalid markup will be corrected on
+   *   The partial HTML snippet to load. Invalid markup will be corrected on
    *   import.
    *
    * @return \DOMDocument
-   *   A \DOMDocument that represents the loaded (X)HTML snippet.
+   *   A \DOMDocument that represents the loaded HTML snippet.
    */
   public static function load($html) {
     $document = <<<EOD
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head>
-<body>!html</body>
+<!DOCTYPE html>
+<html>
+<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>
+<body>$html</body>
 </html>
 EOD;
 
-    // PHP's \DOMDocument::saveXML() encodes carriage returns as &#13; so
-    // normalize all newlines to line feeds.
-    $html = str_replace(["\r\n", "\r"], "\n", $html);
-
-    // PHP's \DOMDocument serialization adds extra whitespace when the markup
-    // of the wrapping document contains newlines, so ensure we remove all
-    // newlines before injecting the actual HTML body to be processed.
-    $document = strtr($document, ["\n" => '', '!html' => $html]);
-
-    $dom = new \DOMDocument();
-    // Ignore warnings during HTML soup loading.
-    @$dom->loadHTML($document, LIBXML_NOBLANKS);
-
-    return $dom;
+    // Instantiate the HTML5 parser, but without the HTML5 namespace being
+    // added to the DOM document.
+    $html5 = new HTML5(['disable_html_ns' => TRUE]);
+    return $html5->loadHTML($document);
   }
 
   /**
    * Converts the body of a \DOMDocument back to an HTML snippet.
    *
-   * The function serializes the body part of a \DOMDocument back to an (X)HTML
-   * snippet. The resulting (X)HTML snippet will be properly formatted to be
+   * The function serializes the body part of a \DOMDocument back to an HTML
+   * snippet. The resulting HTML snippet will be properly formatted to be
    * compatible with HTML user agents.
    *
    * @param \DOMDocument $document
@@ -308,7 +301,7 @@ public static function load($html) {
    *   node will be converted.
    *
    * @return string
-   *   A valid (X)HTML snippet, as a string.
+   *   A valid HTML snippet, as a string.
    */
   public static function serialize(\DOMDocument $document) {
     $body_node = $document->getElementsByTagName('body')->item(0);
@@ -321,10 +314,23 @@ public static function serialize(\DOMDocument $document) {
       foreach ($body_node->getElementsByTagName('style') as $node) {
         static::escapeCdataElement($node, '/*', '*/');
       }
+
+      // Serialize the body using our custom set of rules.
+      // @see \Masterminds\HTML5::saveHTML()
+      $stream = fopen('php://temp', 'wb');
+      $rules = new HtmlSerializerRules($stream);
       foreach ($body_node->childNodes as $node) {
-        $html .= $document->saveXML($node);
+        $traverser = new Traverser($node, $stream, $rules);
+        $traverser->walk();
       }
+      $rules->unsetTraverser();
+      $html = stream_get_contents($stream, -1, 0);
+      fclose($stream);
     }
+
+    // Normalize all newlines.
+    $html = str_replace(["\r\n", "\r"], "\n", $html);
+
     return $html;
   }
 
@@ -455,13 +461,13 @@ public static function escape($text): string {
    * and email.
    *
    * @param string $html
-   *   The partial (X)HTML snippet to load. Invalid markup will be corrected on
+   *   The partial HTML snippet to load. Invalid markup will be corrected on
    *   import.
    * @param string $scheme_and_host
    *   The root URL, which has a URI scheme, host and optional port.
    *
    * @return string
-   *   The updated (X)HTML snippet.
+   *   The updated HTML snippet.
    */
   public static function transformRootRelativeUrlsToAbsolute($html, $scheme_and_host) {
     assert(empty(array_diff(array_keys(parse_url($scheme_and_host)), ["scheme", "host", "port"])), '$scheme_and_host contains scheme, host and port at most.');
@@ -472,23 +478,25 @@ public static function transformRootRelativeUrlsToAbsolute($html, $scheme_and_ho
     $xpath = new \DOMXpath($html_dom);
 
     // Update all root-relative URLs to absolute URLs in the given HTML.
+    // Perform on attributes that may contain a single URI.
     foreach (static::$uriAttributes as $attr) {
       foreach ($xpath->query("//*[starts-with(@$attr, '/') and not(starts-with(@$attr, '//'))]") as $node) {
         $node->setAttribute($attr, $scheme_and_host . $node->getAttribute($attr));
       }
-      foreach ($xpath->query("//*[@srcset]") as $node) {
-        // @see https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset
-        // @see https://html.spec.whatwg.org/multipage/embedded-content.html#image-candidate-string
-        $image_candidate_strings = explode(',', $node->getAttribute('srcset'));
-        $image_candidate_strings = array_map('trim', $image_candidate_strings);
-        for ($i = 0; $i < count($image_candidate_strings); $i++) {
-          $image_candidate_string = $image_candidate_strings[$i];
-          if ($image_candidate_string[0] === '/' && $image_candidate_string[1] !== '/') {
-            $image_candidate_strings[$i] = $scheme_and_host . $image_candidate_string;
-          }
+    }
+    // Perform on each URI within "srcset" attributes.
+    foreach ($xpath->query("//*[@srcset]") as $node) {
+      // @see https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset
+      // @see https://html.spec.whatwg.org/multipage/embedded-content.html#image-candidate-string
+      $image_candidate_strings = explode(',', $node->getAttribute('srcset'));
+      $image_candidate_strings = array_map('trim', $image_candidate_strings);
+      for ($i = 0; $i < count($image_candidate_strings); $i++) {
+        $image_candidate_string = $image_candidate_strings[$i];
+        if ($image_candidate_string[0] === '/' && $image_candidate_string[1] !== '/') {
+          $image_candidate_strings[$i] = $scheme_and_host . $image_candidate_string;
         }
-        $node->setAttribute('srcset', implode(', ', $image_candidate_strings));
       }
+      $node->setAttribute('srcset', implode(', ', $image_candidate_strings));
     }
     return Html::serialize($html_dom);
   }
diff --git a/core/lib/Drupal/Component/Utility/HtmlSerializerRules.php b/core/lib/Drupal/Component/Utility/HtmlSerializerRules.php
new file mode 100644
index 0000000000000000000000000000000000000000..3559850053d6fd9b63eed2a4750ac8afe55111e7
--- /dev/null
+++ b/core/lib/Drupal/Component/Utility/HtmlSerializerRules.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Component\Utility;
+
+use Masterminds\HTML5\Serializer\OutputRules;
+
+/**
+ * Drupal-specific HTML5 serializer rules.
+ *
+ * Drupal's XSS filtering cannot handle entities inside element attribute
+ * values. The XSS filtering was written based on W3C XML recommendations
+ * which constituted that the ampersand character (&) and the angle
+ * brackets (< and >) must not appear in their literal form in attribute
+ * values. This differs from the HTML living standard which permits angle
+ * brackets.
+ *
+ * @see core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/drupalhtmlbuilder.js
+ */
+class HtmlSerializerRules extends OutputRules {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function escape($text, $attribute = FALSE) {
+    $text = parent::escape($text, $attribute);
+
+    if ($attribute) {
+      $text = strtr($text, [
+        '<' => '&lt;',
+        '>' => '&gt;',
+      ]);
+    }
+
+    return $text;
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Utility/composer.json b/core/lib/Drupal/Component/Utility/composer.json
index da6c4a096be8e538942c80506f5badf55842e9cf..f20959e228f865c125e2115b4d1adcc86449b3bd 100644
--- a/core/lib/Drupal/Component/Utility/composer.json
+++ b/core/lib/Drupal/Component/Utility/composer.json
@@ -7,7 +7,8 @@
     "homepage": "https://www.drupal.org/project/drupal",
     "license": "GPL-2.0-or-later",
     "require": {
-        "php": ">=8.1.0"
+        "php": ">=8.1.0",
+        "masterminds/html5": "^2.7"
     },
     "autoload": {
         "psr-4": {
diff --git a/core/lib/Drupal/Core/Access/CustomAccessCheck.php b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
index 353d09cd8060829bdd094ebbfec22bfc30a4a580..3210c4e4c432a14f95e6514e8aa8f4bec04b7cd2 100644
--- a/core/lib/Drupal/Core/Access/CustomAccessCheck.php
+++ b/core/lib/Drupal/Core/Access/CustomAccessCheck.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Controller\ControllerResolverInterface;
 use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
+use Symfony\Component\HttpFoundation\Request;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Symfony\Component\Routing\Route;
@@ -56,11 +57,14 @@ public function __construct(ControllerResolverInterface $controller_resolver, Ac
    *   The route match object to be checked.
    * @param \Drupal\Core\Session\AccountInterface $account
    *   The account being checked.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   Optional, a request. Only supply this parameter when checking the
+   *   incoming request.
    *
    * @return \Drupal\Core\Access\AccessResultInterface
    *   The access result.
    */
-  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account) {
+  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, Request $request = NULL) {
     try {
       $callable = $this->controllerResolver->getControllerFromDefinition($route->getRequirement('_custom_access'));
     }
@@ -69,7 +73,7 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
       throw new \BadMethodCallException(sprintf('The "%s" method is not callable as a _custom_access callback in route "%s"', $route->getRequirement('_custom_access'), $route->getPath()));
     }
 
-    $arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account);
+    $arguments_resolver = $this->argumentsResolverFactory->getArgumentsResolver($route_match, $account, $request);
     $arguments = $arguments_resolver->getArguments($callable);
 
     return call_user_func_array($callable, $arguments);
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
index 8af3dc6d5cfbf60986df1b824911cfaeb11509b7..c0a65b707161cf1b94fda695d98592f75e7a2d72 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
@@ -100,7 +100,7 @@ public function __construct(AssetResolverInterface $asset_resolver, ConfigFactor
     $this->renderer = $renderer;
     $this->moduleHandler = $module_handler;
     if (!isset($languageManager)) {
-      @trigger_error('Calling ' . __METHOD__ . '() without the $language_manager argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0', E_USER_DEPRECATED);
+      @trigger_error('Calling ' . __METHOD__ . '() without the $language_manager argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3347754', E_USER_DEPRECATED);
       $this->languageManager = \Drupal::languageManager();
     }
   }
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php
index b832c26a4c1448f74e2a7ef3cc9b0b3a0b4804b6..ebf819fd5da1baa341f495300868fe96e4658a66 100644
--- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php
+++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php
@@ -131,7 +131,7 @@ public function optimize(array $css_assets, array $libraries) {
    * {@inheritdoc}
    */
   public function getAll() {
-    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.2.0 and will be removed in drupal:11.0.0, there is no replacement. See https:// www.drupal.org/node/3301744', E_USER_DEPRECATED);
+    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3301744', E_USER_DEPRECATED);
     return [];
   }
 
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php
index 94954a446464604c11cfee0a23ba27cde8dd3017..6dff3650f2ce983ed6353d6a375a237a75e71884 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php
@@ -146,7 +146,7 @@ public function optimize(array $js_assets, array $libraries) {
    * {@inheritdoc}
    */
   public function getAll() {
-    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.2.0 and will be removed in drupal:11.0.0, there is no replacement. See https:// www.drupal.org/node/3301744', E_USER_DEPRECATED);
+    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3301744', E_USER_DEPRECATED);
     return [];
   }
 
diff --git a/core/lib/Drupal/Core/Asset/JsOptimizer.php b/core/lib/Drupal/Core/Asset/JsOptimizer.php
index 497add6797943105632a42138e22b7dfc26b8939..83d48ee1a2d3f47667f0d0d8333066d24bc332ee 100644
--- a/core/lib/Drupal/Core/Asset/JsOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/JsOptimizer.php
@@ -3,15 +3,26 @@
 namespace Drupal\Core\Asset;
 
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Utility\Error;
 use Peast\Formatter\Compact as CompactFormatter;
 use Peast\Peast;
 use Peast\Renderer;
+use Peast\Syntax\Exception as PeastSyntaxException;
+use Psr\Log\LoggerInterface;
 
 /**
  * Optimizes a JavaScript asset.
  */
 class JsOptimizer implements AssetOptimizerInterface {
 
+  /**
+   * Constructs a new JsOptimizer object.
+   *
+   * @param \Psr\Log\LoggerInterface $logger
+   *   The logger.
+   */
+  public function __construct(protected readonly LoggerInterface $logger) {}
+
   /**
    * {@inheritdoc}
    */
@@ -34,10 +45,27 @@ public function optimize(array $js_asset) {
       $data = Unicode::convertToUtf8($data, $js_asset['attributes']['charset']);
     }
     // Remove comments, whitespace, and optional braces.
-    $ast = Peast::latest($data)->parse();
-    $renderer = new Renderer();
-    $renderer->setFormatter(new CompactFormatter());
-    return $renderer->render($ast);
+    try {
+      $ast = Peast::latest($data)->parse();
+      $renderer = new Renderer();
+      $renderer->setFormatter(new CompactFormatter());
+      return $renderer->render($ast);
+    }
+    catch (\Exception $exception) {
+      if ($exception instanceof PeastSyntaxException) {
+        $position = $exception->getPosition();
+        Error::logException($this->logger, $exception, 'Syntax error:  @message, File: @asset_file, Line: @asset_line, Column: @asset_column, Index: @asset_index', [
+          '@asset_file' => $js_asset['data'],
+          '@asset_line' => $position->getLine(),
+          '@asset_column' => $position->getColumn(),
+          '@asset_index' => $position->getIndex(),
+        ]);
+      }
+      else {
+        Error::logException($this->logger, $exception);
+      }
+      return $data;
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
index 4c145614d488e3672541c0fbc29f6d4021629679..feb343ec301bb76275b9bce7a5a4ccdc3c2df879 100644
--- a/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
+++ b/core/lib/Drupal/Core/Asset/LibraryDiscovery.php
@@ -60,6 +60,7 @@ public function getLibraryByName($extension, $name) {
       return FALSE;
     }
     if (isset($libraries[$name]['deprecated'])) {
+      // phpcs:ignore Drupal.Semantics.FunctionTriggerError
       @trigger_error(str_replace('%library_id%', "$extension/$name", $libraries[$name]['deprecated']), E_USER_DEPRECATED);
     }
     return $libraries[$name];
diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryCollector.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryCollector.php
index 3634892de9edf5717309285c005574a159a34f73..5379946b0c86e0a0d4b272cee55a05af3beb1a89 100644
--- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryCollector.php
+++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryCollector.php
@@ -139,6 +139,7 @@ protected function applyLibrariesExtend($extension, $library_name, $library_defi
         if (isset($library_definition['deprecated'])) {
           $extend_message = sprintf('Theme "%s" is extending a deprecated library.', $extension);
           $library_deprecation = str_replace('%library_id%', "$extension/$library_name", $library_definition['deprecated']);
+          // phpcs:ignore Drupal.Semantics.FunctionTriggerError
           @trigger_error("$extend_message $library_deprecation", E_USER_DEPRECATED);
         }
         if (!is_string($library_extend_name)) {
diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
index cf0d46be6b487cd98790c9d15771a8307a187fa6..6677ca7d06bfa08a06b689b7d2d92bbcba01db0a 100644
--- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
+++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php
@@ -404,6 +404,7 @@ protected function applyLibrariesOverride($libraries, $extension) {
           if (isset($library['deprecated'])) {
             $override_message = sprintf('Theme "%s" is overriding a deprecated library.', $extension);
             $library_deprecation = str_replace('%library_id%', "$extension/$library_name", $library['deprecated']);
+            // phpcs:ignore Drupal.Semantics.FunctionTriggerError
             @trigger_error("$override_message $library_deprecation", E_USER_DEPRECATED);
           }
           // Active theme defines an override for this library.
diff --git a/core/lib/Drupal/Core/Cache/Cache.php b/core/lib/Drupal/Core/Cache/Cache.php
index 71fbc0db2c928e9e570cac6fa4b7a4143fe93245..f93a4708027ca5a13dad4f8c1a7833c1fc0b1389 100644
--- a/core/lib/Drupal/Core/Cache/Cache.php
+++ b/core/lib/Drupal/Core/Cache/Cache.php
@@ -148,7 +148,7 @@ public static function getBins() {
    *   A hash of the query arguments.
    */
   public static function keyFromQuery(SelectInterface $query) {
-    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. No replacement provided. https://www.drupal.org/node/3322044', E_USER_DEPRECATED);
+    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. No replacement provided. See https://www.drupal.org/node/3322044', E_USER_DEPRECATED);
     $query->preExecute();
     $keys = [(string) $query, $query->getArguments()];
     return hash('sha256', serialize($keys));
diff --git a/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php b/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php
index f75882c5e31cb761be4e1c1ab782757c38454df8..2a14f8a5407eded9a37c27bf2a67821be1e93c87 100644
--- a/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php
+++ b/core/lib/Drupal/Core/Cache/CacheTagsChecksumTrait.php
@@ -68,7 +68,14 @@ public function invalidateTags(array $tags) {
     $in_transaction = $this->getDatabaseConnection()->inTransaction();
     if ($in_transaction) {
       if (empty($this->delayedTags)) {
-        $this->getDatabaseConnection()->addRootTransactionEndCallback([$this, 'rootTransactionEndCallback']);
+        // @todo in drupal:11.0.0, remove the conditional and only call the
+        //   TransactionManager().
+        if ($this->getDatabaseConnection()->transactionManager()) {
+          $this->getDatabaseConnection()->transactionManager()->addPostTransactionCallback([$this, 'rootTransactionEndCallback']);
+        }
+        else {
+          $this->getDatabaseConnection()->addRootTransactionEndCallback([$this, 'rootTransactionEndCallback']);
+        }
       }
       $this->delayedTags = Cache::mergeTags($this->delayedTags, $tags);
     }
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index f8d3f503c7b9f17412b1b1b404252eee78a6ed8b..29e4fe5c7b3edff67832a9cad9c52c5418ec380b 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -32,6 +32,11 @@ class DatabaseBackend implements CacheBackendInterface {
    */
   const MAXIMUM_NONE = -1;
 
+  /**
+   * The chunk size for inserting cache entities.
+   */
+  const MAX_ITEMS_PER_CACHE_SET = 100;
+
   /**
    * The maximum number of rows that this cache bin table is allowed to store.
    *
@@ -215,62 +220,68 @@ public function setMultiple(array $items) {
    * @see \Drupal\Core\Cache\CacheBackendInterface::setMultiple()
    */
   protected function doSetMultiple(array $items) {
-    $values = [];
-
-    foreach ($items as $cid => $item) {
-      $item += [
-        'expire' => CacheBackendInterface::CACHE_PERMANENT,
-        'tags' => [],
-      ];
-
-      assert(Inspector::assertAllStrings($item['tags']), 'Cache Tags must be strings.');
-      $item['tags'] = array_unique($item['tags']);
-      // Sort the cache tags so that they are stored consistently in the DB.
-      sort($item['tags']);
-
-      $fields = [
-        'cid' => $this->normalizeCid($cid),
-        'expire' => $item['expire'],
-        'created' => round(microtime(TRUE), 3),
-        'tags' => implode(' ', $item['tags']),
-        'checksum' => $this->checksumProvider->getCurrentChecksum($item['tags']),
-      ];
-
-      // Avoid useless writes.
-      if ($fields['checksum'] === CacheTagsChecksumInterface::INVALID_CHECKSUM_WHILE_IN_TRANSACTION) {
-        continue;
-      }
+    // Chunk the items as the database might not be able to receive thousands
+    // of items in a single query.
+    $chunks = array_chunk($items, self::MAX_ITEMS_PER_CACHE_SET, TRUE);
+
+    foreach ($chunks as $chunk_items) {
+      $values = [];
+
+      foreach ($chunk_items as $cid => $item) {
+        $item += [
+          'expire' => CacheBackendInterface::CACHE_PERMANENT,
+          'tags' => [],
+        ];
+
+        assert(Inspector::assertAllStrings($item['tags']), 'Cache Tags must be strings.');
+        $item['tags'] = array_unique($item['tags']);
+        // Sort the cache tags so that they are stored consistently in the DB.
+        sort($item['tags']);
+
+        $fields = [
+          'cid' => $this->normalizeCid($cid),
+          'expire' => $item['expire'],
+          'created' => round(microtime(TRUE), 3),
+          'tags' => implode(' ', $item['tags']),
+          'checksum' => $this->checksumProvider->getCurrentChecksum($item['tags']),
+        ];
+
+        // Avoid useless writes.
+        if ($fields['checksum'] === CacheTagsChecksumInterface::INVALID_CHECKSUM_WHILE_IN_TRANSACTION) {
+          continue;
+        }
 
-      if (!is_string($item['data'])) {
-        $fields['data'] = serialize($item['data']);
-        $fields['serialized'] = 1;
+        if (!is_string($item['data'])) {
+          $fields['data'] = serialize($item['data']);
+          $fields['serialized'] = 1;
+        }
+        else {
+          $fields['data'] = $item['data'];
+          $fields['serialized'] = 0;
+        }
+        $values[] = $fields;
       }
-      else {
-        $fields['data'] = $item['data'];
-        $fields['serialized'] = 0;
+
+      // If all $items were useless writes, we may end up with zero writes.
+      if (count($values) === 0) {
+        return;
       }
-      $values[] = $fields;
-    }
 
-    // If all $items were useless writes, we may end up with zero writes.
-    if (empty($values)) {
-      return;
-    }
+      // Use an upsert query which is atomic and optimized for multiple-row
+      // merges.
+      $query = $this->connection
+        ->upsert($this->bin)
+        ->key('cid')
+        ->fields(['cid', 'expire', 'created', 'tags', 'checksum', 'data', 'serialized']);
+      foreach ($values as $fields) {
+        // Only pass the values since the order of $fields matches the order of
+        // the insert fields. This is a performance optimization to avoid
+        // unnecessary loops within the method.
+        $query->values(array_values($fields));
+      }
 
-    // Use an upsert query which is atomic and optimized for multiple-row
-    // merges.
-    $query = $this->connection
-      ->upsert($this->bin)
-      ->key('cid')
-      ->fields(['cid', 'expire', 'created', 'tags', 'checksum', 'data', 'serialized']);
-    foreach ($values as $fields) {
-      // Only pass the values since the order of $fields matches the order of
-      // the insert fields. This is a performance optimization to avoid
-      // unnecessary loops within the method.
-      $query->values(array_values($fields));
+      $query->execute();
     }
-
-    $query->execute();
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index 5118607a1046f7ad9a6f48e2288c35a31f9a2430..a7f4ce4dd4d1c43d847632ff7385bac75b1ce7fe 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -3,6 +3,7 @@
 namespace Drupal\Core\Config;
 
 use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Config\Entity\ConfigDependencyManager;
 use Drupal\Core\Extension\ExtensionPathResolver;
 use Drupal\Core\Installer\InstallerKernel;
@@ -620,7 +621,7 @@ protected function getMissingDependencies($config_name, array $data, array $enab
 
       // Ensure enforced dependencies are included.
       if (isset($all_dependencies['enforced'])) {
-        $all_dependencies = array_merge($all_dependencies, $data['dependencies']['enforced']);
+        $all_dependencies = NestedArray::mergeDeep($all_dependencies, $data['dependencies']['enforced']);
         unset($all_dependencies['enforced']);
       }
       // Ensure the configuration entity type provider is in the list of
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
index d4c092a8594898abfa13ab159eca5ffa4b49b511..be5840f77b47e858b542765e2a258a5bdd09d0bb 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php
@@ -395,7 +395,7 @@ public function calculateDependencies() {
   /**
    * {@inheritdoc}
    */
-  public function toUrl($rel = 'edit-form', array $options = []) {
+  public function toUrl($rel = NULL, array $options = []) {
     // Unless language was already provided, avoid setting an explicit language.
     $options += ['language' => NULL];
     return parent::toUrl($rel, $options);
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
index a34ee16a59ef0d9836a0f33dcda8a92c225147e1..53ee5136ed9bdb0afc8cb6a8eefbe03ded6ab525 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
@@ -183,4 +183,23 @@ public function getLookupKeys() {
     return $this->lookup_keys;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getConstraints() {
+    $constraints = parent::getConstraints();
+
+    // If there is an ID key for this config entity type, make it immutable by
+    // default. Individual config entities can override this with an
+    // `ImmutableProperties` constraint in their definition that is either empty,
+    // or with an alternative set of immutable properties.
+    $id_key = $this->getKey('id');
+    if ($id_key) {
+      $constraints += [
+        'ImmutableProperties' => [$id_key],
+      ];
+    }
+    return $constraints;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
index 7790e3500d38a0e40f94ff113ed462f93a650862..261f4ae0707fceb3e3789d959eada38729b877bc 100644
--- a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
+++ b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
@@ -101,7 +101,7 @@ public function checkConfigSchema(TypedConfigManagerInterface $typed_config, $co
     // failing the test (which would be too disruptive for the ecosystem), but
     // trigger a deprecation notice instead.
     if (!empty($validation_errors) && $this->isContribViolation()) {
-      @trigger_error(sprintf("The '%s' configuration contains validation errors. Invalid config is deprecated in drupal:10.2.0 and will be required to be valid in drupal:11.0.0. The following validation errors were found:\n\t\t- %s",
+      @trigger_error(sprintf("The '%s' configuration contains validation errors. Invalid config is deprecated in drupal:10.2.0 and will be required to be valid in drupal:11.0.0. The following validation errors were found:\n\t\t- %s\nSee https://www.drupal.org/node/3362879",
         $config_name,
         implode("\n\t\t- ", $validation_errors)
       ), E_USER_DEPRECATED);
diff --git a/core/lib/Drupal/Core/Config/Schema/Sequence.php b/core/lib/Drupal/Core/Config/Schema/Sequence.php
index 8e715baf6704fe527315de66d54a628ac3ea9177..66a5c3a36f859890d3bf3dbb88ba0595bd6fe7d1 100644
--- a/core/lib/Drupal/Core/Config/Schema/Sequence.php
+++ b/core/lib/Drupal/Core/Config/Schema/Sequence.php
@@ -28,6 +28,8 @@ protected function getElementDefinition($key) {
     $definition = [];
     if (isset($this->definition['sequence'][0])) {
       $definition = $this->definition['sequence'][0];
+      $bc_sequence_location = $this->getPropertyPath();
+      @trigger_error("The definition for the '$bc_sequence_location' sequence declares the type of its items in a way that is deprecated in drupal:8.0.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/2442603", E_USER_DEPRECATED);
     }
     elseif ($this->definition['sequence']) {
       $definition = $this->definition['sequence'];
diff --git a/core/lib/Drupal/Core/Controller/ArgumentResolver/Psr7RequestValueResolver.php b/core/lib/Drupal/Core/Controller/ArgumentResolver/Psr7RequestValueResolver.php
index 11b7285fdbd117090440348f91b65cf8c44b16fa..279404c818754fd0cc073e2e4eddd516b984ab9a 100644
--- a/core/lib/Drupal/Core/Controller/ArgumentResolver/Psr7RequestValueResolver.php
+++ b/core/lib/Drupal/Core/Controller/ArgumentResolver/Psr7RequestValueResolver.php
@@ -5,14 +5,13 @@
 use Psr\Http\Message\ServerRequestInterface;
 use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
 use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
 use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
 
 /**
  * Yields a PSR7 request object based on the request object passed along.
  */
-final class Psr7RequestValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface {
+final class Psr7RequestValueResolver implements ValueResolverInterface {
 
   /**
    * The PSR-7 converter.
@@ -33,8 +32,14 @@ public function __construct(HttpMessageFactoryInterface $http_message_factory) {
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0.
+   *    There is no replacement.
+   *
+   * @see https://www.drupal.org/node/3383585
    */
   public function supports(Request $request, ArgumentMetadata $argument): bool {
+    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3383585', E_USER_DEPRECATED);
     return $argument->getType() == ServerRequestInterface::class;
   }
 
diff --git a/core/lib/Drupal/Core/Controller/ArgumentResolver/RouteMatchValueResolver.php b/core/lib/Drupal/Core/Controller/ArgumentResolver/RouteMatchValueResolver.php
index 51c460e095e6dcfe33dea2c10fc334b11f5362fc..12b0b7b4a0e1d7f32bbd2623aa5b41f48298e350 100644
--- a/core/lib/Drupal/Core/Controller/ArgumentResolver/RouteMatchValueResolver.php
+++ b/core/lib/Drupal/Core/Controller/ArgumentResolver/RouteMatchValueResolver.php
@@ -5,19 +5,24 @@
 use Drupal\Core\Routing\RouteMatch;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
 use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
 use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
 
 /**
  * Yields a RouteMatch object based on the request object passed along.
  */
-final class RouteMatchValueResolver implements ArgumentValueResolverInterface, ValueResolverInterface {
+final class RouteMatchValueResolver implements ValueResolverInterface {
 
   /**
    * {@inheritdoc}
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0.
+   *    There is no replacement.
+   *
+   * @see https://www.drupal.org/node/3383585
    */
   public function supports(Request $request, ArgumentMetadata $argument): bool {
+    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3383585', E_USER_DEPRECATED);
     return $argument->getType() == RouteMatchInterface::class || is_subclass_of($argument->getType(), RouteMatchInterface::class);
   }
 
diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index aecaed728ffcadf7faad6385c68243c0758ac9ad..97b528c25bed0caa25aa812a9f9dcf232e12253c 100644
--- a/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Database\Query\Truncate;
 use Drupal\Core\Database\Query\Update;
 use Drupal\Core\Database\Query\Upsert;
+use Drupal\Core\Database\Transaction\TransactionManagerInterface;
 use Drupal\Core\Pager\PagerManagerInterface;
 
 /**
@@ -62,6 +63,11 @@ abstract class Connection {
    * transaction.
    *
    * @var array
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. The
+   *   transaction stack is now managed by TransactionManager.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   protected $transactionLayers = [];
 
@@ -204,6 +210,11 @@ abstract class Connection {
    * Post-root (non-nested) transaction commit callbacks.
    *
    * @var callable[]
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. The
+   *   transaction end callbacks are now managed by TransactionManager.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   protected $rootTransactionEndCallbacks = [];
 
@@ -226,6 +237,11 @@ abstract class Connection {
    */
   private array $enabledEvents = [];
 
+  /**
+   * The transaction manager.
+   */
+  protected TransactionManagerInterface|FALSE $transactionManager;
+
   /**
    * Constructs a Connection object.
    *
@@ -276,6 +292,20 @@ public function __destruct() {
     $this->connection = NULL;
   }
 
+  /**
+   * Returns the client-level database connection object.
+   *
+   * This method should normally be used only within database driver code. Not
+   * doing so constitutes a risk of introducing code that is not database
+   * independent.
+   *
+   * @return object
+   *   The client-level database connection, for example \PDO.
+   */
+  public function getClientConnection(): object {
+    return $this->connection;
+  }
+
   /**
    * Returns the default query options for any given query.
    *
@@ -1337,6 +1367,41 @@ public function escapeLike($string) {
     return addcslashes($string, '\%_');
   }
 
+  /**
+   * Returns the transaction manager.
+   *
+   * @return \Drupal\Core\Database\Transaction\TransactionManagerInterface|false
+   *   The transaction manager, or FALSE if not available.
+   */
+  public function transactionManager(): TransactionManagerInterface|FALSE {
+    if (!isset($this->transactionManager)) {
+      try {
+        $this->transactionManager = $this->driverTransactionManager();
+      }
+      catch (\LogicException $e) {
+        $this->transactionManager = FALSE;
+      }
+    }
+    return $this->transactionManager;
+  }
+
+  /**
+   * Returns a new instance of the driver's transaction manager.
+   *
+   * Database drivers must implement their own class extending from
+   * \Drupal\Core\Database\Transaction\TransactionManagerBase, and instantiate
+   * it here.
+   *
+   * @return \Drupal\Core\Database\Transaction\TransactionManagerInterface
+   *   The transaction manager.
+   *
+   * @throws \LogicException
+   *   If the transaction manager is undefined or unavailable.
+   */
+  protected function driverTransactionManager(): TransactionManagerInterface {
+    throw new \LogicException('The database driver has no TransactionManager implementation');
+  }
+
   /**
    * Determines if there is an active transaction open.
    *
@@ -1344,7 +1409,13 @@ public function escapeLike($string) {
    *   TRUE if we're currently in a transaction, FALSE otherwise.
    */
   public function inTransaction() {
+    if ($this->transactionManager()) {
+      return $this->transactionManager()->inTransaction();
+    }
+    // Start of BC layer.
+    // @phpstan-ignore-next-line
     return ($this->transactionDepth() > 0);
+    // End of BC layer.
   }
 
   /**
@@ -1352,8 +1423,17 @@ public function inTransaction() {
    *
    * @return int
    *   The current transaction depth.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not
+   *   access the transaction stack depth, it is an implementation detail.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   public function transactionDepth() {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not access the transaction stack depth, it is an implementation detail. See https://www.drupal.org/node/3381002', E_USER_DEPRECATED);
+    if ($this->transactionManager()) {
+      return $this->transactionManager()->stackDepth();
+    }
     return count($this->transactionLayers);
   }
 
@@ -1368,9 +1448,12 @@ public function transactionDepth() {
    *
    * @see \Drupal\Core\Database\Transaction
    *
-   * @todo in drupal:11.0.0, return a new Transaction instance directly.
+   * @todo in drupal:11.0.0, push to the TransactionManager directly.
    */
   public function startTransaction($name = '') {
+    if ($this->transactionManager()) {
+      return $this->transactionManager()->push($name);
+    }
     $class = $this->getDriverClass('Transaction');
     return new $class($this, $name);
   }
@@ -1388,8 +1471,18 @@ public function startTransaction($name = '') {
    * @throws \Drupal\Core\Database\TransactionNoActiveException
    *
    * @see \Drupal\Core\Database\Transaction::rollBack()
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not
+   *   rollback the connection, roll back the Transaction objects instead.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   public function rollBack($savepoint_name = 'drupal_transaction') {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not rollback the connection, roll back the Transaction objects instead. See https://www.drupal.org/node/3381002', E_USER_DEPRECATED);
+    if ($this->transactionManager()) {
+      $this->transactionManager()->rollback($savepoint_name, 'bc-force-rollback');
+      return;
+    }
     if (!$this->inTransaction()) {
       throw new TransactionNoActiveException();
     }
@@ -1447,8 +1540,14 @@ public function rollBack($savepoint_name = 'drupal_transaction') {
    * @throws \Drupal\Core\Database\TransactionNameNonUniqueException
    *
    * @see \Drupal\Core\Database\Transaction
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   TransactionManagerInterface methods instead.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   public function pushTransaction($name) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002', E_USER_DEPRECATED);
     if (isset($this->transactionLayers[$name])) {
       throw new TransactionNameNonUniqueException($name . " is already in use.");
     }
@@ -1477,8 +1576,14 @@ public function pushTransaction($name) {
    * @throws \Drupal\Core\Database\TransactionCommitFailedException
    *
    * @see \Drupal\Core\Database\Transaction
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   TransactionManagerInterface methods instead.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   public function popTransaction($name) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002', E_USER_DEPRECATED);
     // The transaction has already been committed earlier. There is nothing we
     // need to do. If this transaction was part of an earlier out-of-order
     // rollback, an exception would already have been thrown by
@@ -1512,8 +1617,18 @@ public function popTransaction($name) {
    *   The callback to invoke.
    *
    * @see \Drupal\Core\Database\Connection::doCommit()
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   TransactionManagerInterface::addPostTransactionCallback() instead.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   public function addRootTransactionEndCallback(callable $callback) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface::addPostTransactionCallback() instead. See https://www.drupal.org/node/3381002', E_USER_DEPRECATED);
+    if ($this->transactionManager()) {
+      $this->transactionManager()->addPostTransactionCallback($callback);
+      return;
+    }
     if (!$this->transactionLayers) {
       throw new \LogicException('Root transaction end callbacks can only be added when there is an active transaction.');
     }
@@ -1524,8 +1639,14 @@ public function addRootTransactionEndCallback(callable $callback) {
    * Commit all the transaction layers that can commit.
    *
    * @internal
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   TransactionManagerInterface methods instead.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   protected function popCommittableTransactions() {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002', E_USER_DEPRECATED);
     // Commit all the committable layers.
     foreach (array_reverse($this->transactionLayers) as $name => $active) {
       // Stop once we found an active transaction.
@@ -1548,8 +1669,14 @@ protected function popCommittableTransactions() {
    * Do the actual commit, invoke post-commit callbacks.
    *
    * @internal
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   TransactionManagerInterface methods instead.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   protected function doCommit() {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002', E_USER_DEPRECATED);
     $success = $this->connection->commit();
     if (!empty($this->rootTransactionEndCallbacks)) {
       $callbacks = $this->rootTransactionEndCallbacks;
@@ -1687,8 +1814,14 @@ abstract public function mapConditionOperator($operator);
    * @throws \Drupal\Core\Database\TransactionExplicitCommitNotAllowedException
    *
    * @see \Drupal\Core\Database\Transaction
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not
+   *   commit the connection, void the Transaction objects instead.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   public function commit() {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not commit the connection, void the Transaction objects instead. See https://www.drupal.org/node/3381002', E_USER_DEPRECATED);
     throw new TransactionExplicitCommitNotAllowedException();
   }
 
diff --git a/core/lib/Drupal/Core/Database/FetchModeTrait.php b/core/lib/Drupal/Core/Database/FetchModeTrait.php
index d74f0ef8f128b82e1687554182e58ccb3a34218c..b9e0018d519b3d68b42a6c9f8fd2c2d71830a38f 100644
--- a/core/lib/Drupal/Core/Database/FetchModeTrait.php
+++ b/core/lib/Drupal/Core/Database/FetchModeTrait.php
@@ -7,6 +7,41 @@
  */
 trait FetchModeTrait {
 
+  /**
+   * Map FETCH_* modes to their literal for inclusion in messages.
+   *
+   * @see https://github.com/php/php-src/blob/master/ext/pdo/php_pdo_driver.h#L65-L80
+   */
+  protected array $fetchModeLiterals = [
+    \PDO::FETCH_DEFAULT => 'FETCH_DEFAULT',
+    \PDO::FETCH_LAZY => 'FETCH_LAZY',
+    \PDO::FETCH_ASSOC => 'FETCH_ASSOC',
+    \PDO::FETCH_NUM => 'FETCH_NUM',
+    \PDO::FETCH_BOTH => 'FETCH_BOTH',
+    \PDO::FETCH_OBJ => 'FETCH_OBJ',
+    \PDO::FETCH_BOUND => 'FETCH_BOUND',
+    \PDO::FETCH_COLUMN => 'FETCH_COLUMN',
+    \PDO::FETCH_CLASS => 'FETCH_CLASS',
+    \PDO::FETCH_INTO => 'FETCH_INTO',
+    \PDO::FETCH_FUNC => 'FETCH_FUNC',
+    \PDO::FETCH_NAMED => 'FETCH_NAMED',
+    \PDO::FETCH_KEY_PAIR => 'FETCH_KEY_PAIR',
+    \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE => 'FETCH_CLASS | FETCH_CLASSTYPE',
+    \PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE => 'FETCH_CLASS | FETCH_PROPS_LATE',
+  ];
+
+  /**
+   * The fetch modes supported.
+   */
+  protected array $supportedFetchModes = [
+    \PDO::FETCH_ASSOC,
+    \PDO::FETCH_CLASS,
+    \PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE,
+    \PDO::FETCH_COLUMN,
+    \PDO::FETCH_NUM,
+    \PDO::FETCH_OBJ,
+  ];
+
   /**
    * Converts a row of data in FETCH_ASSOC format to FETCH_BOTH.
    *
@@ -15,8 +50,14 @@ trait FetchModeTrait {
    *
    * @return array
    *   The row in FETCH_BOTH format.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   supported modes only.
+   *
+   * @see https://www.drupal.org/node/3377999
    */
   protected function assocToBoth(array $rowAssoc): array {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
     // \PDO::FETCH_BOTH returns an array indexed by both the column name
     // and the column number.
     return $rowAssoc + array_values($rowAssoc);
@@ -79,8 +120,14 @@ protected function assocToClass(array $rowAssoc, string $className, array $const
    *
    * @return object
    *   The row in FETCH_CLASS format.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   supported modes only.
+   *
+   * @see https://www.drupal.org/node/3377999
    */
   protected function assocToClassType(array $rowAssoc, array $constructorArguments): object {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
     $className = array_shift($rowAssoc);
     return $this->assocToClass($rowAssoc, $className, $constructorArguments);
   }
@@ -95,8 +142,14 @@ protected function assocToClassType(array $rowAssoc, array $constructorArguments
    *
    * @return object
    *   The object receiving the data.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   supported modes only.
+   *
+   * @see https://www.drupal.org/node/3377999
    */
   protected function assocIntoObject(array $rowAssoc, object $object): object {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
     foreach ($rowAssoc as $column => $value) {
       $object->$column = $value;
     }
diff --git a/core/lib/Drupal/Core/Database/Query/Merge.php b/core/lib/Drupal/Core/Database/Query/Merge.php
index 88681e30cfe4d2bab4d6d06bbe6bfec3bd2c6473..6d1b96e7c427f9276d2a934181bfffca79802486 100644
--- a/core/lib/Drupal/Core/Database/Query/Merge.php
+++ b/core/lib/Drupal/Core/Database/Query/Merge.php
@@ -333,6 +333,7 @@ public function keys(array $fields, array $values = []) {
   public function key($field, $value = NULL) {
     // @todo D9: Remove this backwards-compatibility shim.
     if (is_array($field)) {
+      @trigger_error("Passing an array to the \$field argument of " . __METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/2205327', E_USER_DEPRECATED);
       $this->keys($field, $value ?? []);
     }
     else {
diff --git a/core/lib/Drupal/Core/Database/StatementInterface.php b/core/lib/Drupal/Core/Database/StatementInterface.php
index b7328ad5910ab1aa89bf95a2d12b450095b46a9e..66c53076d50a07fc08a5c3e5d1f66c47df9e9030 100644
--- a/core/lib/Drupal/Core/Database/StatementInterface.php
+++ b/core/lib/Drupal/Core/Database/StatementInterface.php
@@ -125,7 +125,7 @@ public function fetchField($index = 0);
    *
    * @param string|null $class_name
    *   Name of the created class.
-   * @param array|null $constructor_arguments
+   * @param array $constructor_arguments
    *   Elements of this array are passed to the constructor.
    * phpcs:enable
    *
@@ -133,7 +133,7 @@ public function fetchField($index = 0);
    *   The object of specified class or \stdClass if not specified. Returns
    *   FALSE or NULL if there is no next row.
    */
-  public function fetchObject(/* string $class_name = NULL, array $constructor_arguments = NULL */);
+  public function fetchObject(/* string $class_name = NULL, array $constructor_arguments = [] */);
 
   /**
    * Fetches the next row and returns it as an associative array.
diff --git a/core/lib/Drupal/Core/Database/StatementPrefetch.php b/core/lib/Drupal/Core/Database/StatementPrefetch.php
index 7a64b49757a6b85688ecfb956348373f0840f90f..b717da2efb676de3235cdc312e56359343777f20 100644
--- a/core/lib/Drupal/Core/Database/StatementPrefetch.php
+++ b/core/lib/Drupal/Core/Database/StatementPrefetch.php
@@ -470,7 +470,7 @@ public function fetchField($index = 0) {
   /**
    * {@inheritdoc}
    */
-  public function fetchObject(string $class_name = NULL, array $constructor_arguments = NULL) {
+  public function fetchObject(string $class_name = NULL, array $constructor_arguments = []) {
     if (isset($this->currentRow)) {
       if (!isset($class_name)) {
         // Directly cast to an object to avoid a function call.
diff --git a/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php b/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php
index e88b3131173a56c6ced97c37c732acdaca915fa7..20a3378302f938ebcde46591159bf26bde380af8 100644
--- a/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php
+++ b/core/lib/Drupal/Core/Database/StatementPrefetchIterator.php
@@ -188,6 +188,9 @@ public function getQueryString() {
    * {@inheritdoc}
    */
   public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
+    if (!in_array($mode, $this->supportedFetchModes)) {
+      @trigger_error('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
+    }
     $this->defaultFetchStyle = $mode;
     switch ($mode) {
       case \PDO::FETCH_CLASS:
@@ -237,14 +240,18 @@ public function fetch($fetch_style = NULL, $cursor_orientation = \PDO::FETCH_ORI
 
     // Now, format the next prefetched record according to the required fetch
     // style.
+    // @todo in Drupal 11, remove arms for deprecated fetch modes.
     $rowAssoc = $this->data[$currentKey];
     $row = match($fetch_style ?? $this->defaultFetchStyle) {
       \PDO::FETCH_ASSOC => $rowAssoc,
+      // @phpstan-ignore-next-line
       \PDO::FETCH_BOTH => $this->assocToBoth($rowAssoc),
       \PDO::FETCH_NUM => $this->assocToNum($rowAssoc),
       \PDO::FETCH_LAZY, \PDO::FETCH_OBJ => $this->assocToObj($rowAssoc),
+      // @phpstan-ignore-next-line
       \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE => $this->assocToClassType($rowAssoc, $this->fetchOptions['constructor_args']),
       \PDO::FETCH_CLASS => $this->assocToClass($rowAssoc, $this->fetchOptions['class'], $this->fetchOptions['constructor_args']),
+      // @phpstan-ignore-next-line
       \PDO::FETCH_INTO => $this->assocIntoObject($rowAssoc, $this->fetchOptions['object']),
       \PDO::FETCH_COLUMN => $this->assocToColumn($rowAssoc, $this->columnNames, $this->fetchOptions['column']),
       // @todo in Drupal 11, throw an exception if the fetch style cannot be
@@ -275,7 +282,7 @@ public function fetchField($index = 0) {
   /**
    * {@inheritdoc}
    */
-  public function fetchObject(string $class_name = NULL, array $constructor_arguments = NULL) {
+  public function fetchObject(string $class_name = NULL, array $constructor_arguments = []) {
     if (!isset($class_name)) {
       return $this->fetch(\PDO::FETCH_OBJ);
     }
@@ -297,6 +304,9 @@ public function fetchAssoc() {
    * {@inheritdoc}
    */
   public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) {
+    if (isset($mode) && !in_array($mode, $this->supportedFetchModes)) {
+      @trigger_error('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
+    }
     $fetchStyle = $mode ?? $this->defaultFetchStyle;
     if (isset($column_index)) {
       $this->fetchOptions['column'] = $column_index;
diff --git a/core/lib/Drupal/Core/Database/StatementWrapper.php b/core/lib/Drupal/Core/Database/StatementWrapper.php
index d8e7b1c39883dc9308a49fc8d383cce56d3df6b1..965e68fce359840420a21d35d5caec225d2f7c6f 100644
--- a/core/lib/Drupal/Core/Database/StatementWrapper.php
+++ b/core/lib/Drupal/Core/Database/StatementWrapper.php
@@ -193,7 +193,7 @@ public function fetchAssoc() {
   /**
    * {@inheritdoc}
    */
-  public function fetchObject(string $class_name = NULL, array $constructor_arguments = NULL) {
+  public function fetchObject(string $class_name = NULL, array $constructor_arguments = []) {
     if ($class_name) {
       return $this->clientStatement->fetchObject($class_name, $constructor_arguments);
     }
diff --git a/core/lib/Drupal/Core/Database/StatementWrapperIterator.php b/core/lib/Drupal/Core/Database/StatementWrapperIterator.php
index 440b27e96da43b3b19b99dc502ce441d3f4fcac4..b0474045a3752f096702499f9f7a29a3e1e59379 100644
--- a/core/lib/Drupal/Core/Database/StatementWrapperIterator.php
+++ b/core/lib/Drupal/Core/Database/StatementWrapperIterator.php
@@ -28,6 +28,7 @@
 class StatementWrapperIterator implements \Iterator, StatementInterface {
 
   use StatementIteratorTrait;
+  use FetchModeTrait;
 
   /**
    * The client database Statement object.
@@ -214,7 +215,7 @@ public function fetchAssoc() {
   /**
    * {@inheritdoc}
    */
-  public function fetchObject(string $class_name = NULL, array $constructor_arguments = NULL) {
+  public function fetchObject(string $class_name = NULL, array $constructor_arguments = []) {
     if ($class_name) {
       $row = $this->clientStatement->fetchObject($class_name, $constructor_arguments);
     }
@@ -248,6 +249,9 @@ public function rowCount() {
    * {@inheritdoc}
    */
   public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
+    if (!in_array($mode, $this->supportedFetchModes)) {
+      @trigger_error('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
+    }
     // Call \PDOStatement::setFetchMode to set fetch mode.
     // \PDOStatement is picky about the number of arguments in some cases so we
     // need to be pass the exact number of arguments we where given.
@@ -262,6 +266,9 @@ public function setFetchMode($mode, $a1 = NULL, $a2 = []) {
    * {@inheritdoc}
    */
   public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL) {
+    if (isset($mode) && !in_array($mode, $this->supportedFetchModes)) {
+      @trigger_error('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
+    }
     // Call \PDOStatement::fetchAll to fetch all rows.
     // \PDOStatement is picky about the number of arguments in some cases so we
     // need to pass the exact number of arguments we were given.
@@ -285,6 +292,9 @@ public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset =
    * {@inheritdoc}
    */
   public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) {
+    if (isset($mode) && !in_array($mode, $this->supportedFetchModes)) {
+      @trigger_error('Fetch mode ' . ($this->fetchModeLiterals[$mode] ?? $mode) . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999', E_USER_DEPRECATED);
+    }
     // Call \PDOStatement::fetchAll to fetch all rows.
     // \PDOStatement is picky about the number of arguments in some cases so we
     // need to be pass the exact number of arguments we where given.
diff --git a/core/lib/Drupal/Core/Database/Transaction.php b/core/lib/Drupal/Core/Database/Transaction.php
index 76d5fc8f5fac41f9e1e8587718a12b8863716913..062dd177d09e6ee3a63af1bcb6acf4d1de46c153 100644
--- a/core/lib/Drupal/Core/Database/Transaction.php
+++ b/core/lib/Drupal/Core/Database/Transaction.php
@@ -34,6 +34,11 @@ class Transaction {
    * A boolean value to indicate whether this transaction has been rolled back.
    *
    * @var bool
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is
+   *   no replacement.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   protected $rolledBack = FALSE;
 
@@ -47,10 +52,21 @@ class Transaction {
    */
   protected $name;
 
-  public function __construct(Connection $connection, $name = NULL) {
+  public function __construct(
+    Connection $connection,
+    $name = NULL,
+    protected readonly string $id = '',
+  ) {
+    if ($connection->transactionManager()) {
+      $this->connection = $connection;
+      $this->name = $name;
+      return;
+    }
+    // Start of BC layer.
     $this->connection = $connection;
     // If there is no transaction depth, then no transaction has started. Name
     // the transaction 'drupal_transaction'.
+    // @phpstan-ignore-next-line
     if (!$depth = $connection->transactionDepth()) {
       $this->name = 'drupal_transaction';
     }
@@ -62,14 +78,24 @@ public function __construct(Connection $connection, $name = NULL) {
     else {
       $this->name = $name;
     }
+    // @phpstan-ignore-next-line
     $this->connection->pushTransaction($this->name);
+    // End of BC layer.
   }
 
   public function __destruct() {
+    if ($this->connection->transactionManager()) {
+      $this->connection->transactionManager()->unpile($this->name, $this->id);
+      return;
+    }
+    // Start of BC layer.
     // If we rolled back then the transaction would have already been popped.
+    // @phpstan-ignore-next-line
     if (!$this->rolledBack) {
+      // @phpstan-ignore-next-line
       $this->connection->popTransaction($this->name);
     }
+    // End of BC layer.
   }
 
   /**
@@ -90,8 +116,16 @@ public function name() {
    * @see \Drupal\Core\Database\Connection::rollBack()
    */
   public function rollBack() {
+    if ($this->connection->transactionManager()) {
+      $this->connection->transactionManager()->rollback($this->name, $this->id);
+      return;
+    }
+    // Start of BC layer.
+    // @phpstan-ignore-next-line
     $this->rolledBack = TRUE;
+    // @phpstan-ignore-next-line
     $this->connection->rollBack($this->name);
+    // End of BC layer.
   }
 
 }
diff --git a/core/lib/Drupal/Core/Database/Transaction/ClientConnectionTransactionState.php b/core/lib/Drupal/Core/Database/Transaction/ClientConnectionTransactionState.php
new file mode 100644
index 0000000000000000000000000000000000000000..34712f2df4a85aba01e4cc48981f62f58c68eef8
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/Transaction/ClientConnectionTransactionState.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Enumeration of the possible states of a client connection transaction.
+ */
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Database\Transaction;
+
+/**
+ * Enumeration of the possible states of a client connection transaction.
+ */
+enum ClientConnectionTransactionState {
+
+  case Active;
+  case RolledBack;
+  case RollbackFailed;
+  case Committed;
+  case CommitFailed;
+
+  // In some cases the active transaction can be automatically committed by
+  // the database server (for example, MySql when a DDL statement is executed
+  // during a transaction). We track such cases with 'Voided' when we can
+  // detect them.
+  case Voided;
+
+}
diff --git a/core/lib/Drupal/Core/Database/Transaction/StackItem.php b/core/lib/Drupal/Core/Database/Transaction/StackItem.php
new file mode 100644
index 0000000000000000000000000000000000000000..30b9126e7ca1bb1d2b93af7cb513516b16c97981
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/Transaction/StackItem.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Database\Transaction;
+
+/**
+ * A value object for items on the transaction stack.
+ */
+final class StackItem {
+
+  /**
+   * Constructor.
+   *
+   * @param string $name
+   *   The name of the transaction.
+   * @param StackItemType $type
+   *   The stack item type.
+   */
+  public function __construct(
+    public readonly string $name,
+    public readonly StackItemType $type,
+  ) {
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Database/Transaction/StackItemType.php b/core/lib/Drupal/Core/Database/Transaction/StackItemType.php
new file mode 100644
index 0000000000000000000000000000000000000000..33cfb07fac58ee4591db23ca169f0c8ddaa811bd
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/Transaction/StackItemType.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Enumeration of the types of items in the Drupal transaction stack.
+ */
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Database\Transaction;
+
+/**
+ * Enumeration of the types of items in the Drupal transaction stack.
+ */
+enum StackItemType {
+
+  case Root;
+  case Savepoint;
+
+}
diff --git a/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php b/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..5145a8412fd9ad449726b1e952440a020da0883e
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/Transaction/TransactionManagerBase.php
@@ -0,0 +1,464 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Database\Transaction;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Transaction;
+use Drupal\Core\Database\TransactionCommitFailedException;
+use Drupal\Core\Database\TransactionNameNonUniqueException;
+use Drupal\Core\Database\TransactionNoActiveException;
+use Drupal\Core\Database\TransactionOutOfOrderException;
+
+/**
+ * The database transaction manager base class.
+ *
+ * On many databases transactions cannot nest. Instead, we track nested calls
+ * to transactions and collapse them into a single client transaction.
+ *
+ * Database drivers must implement their own class extending from this, and
+ * instantiate it via their Connection::driverTransactionManager() method.
+ *
+ * @see \Drupal\Core\Database\Connection::driverTransactionManager()
+ */
+abstract class TransactionManagerBase implements TransactionManagerInterface {
+
+  /**
+   * The stack of Drupal transactions currently active.
+   *
+   * This property is keeping track of the Transaction objects started and
+   * ended as a LIFO (Last In, First Out) stack.
+   *
+   * The database API allows to begin transactions, add an arbitrary number of
+   * additional savepoints, and release any savepoint in the sequence. When
+   * this happens, the database will implicitly release all the savepoints
+   * created after the one released. Given Drupal implementation of the
+   * Transaction objects, we cannot force descoping of the corresponding
+   * Transaction savepoint objects from the manager, because they live in the
+   * scope of the calling code. This stack ensures that when an outlived
+   * Transaction object gets out of scope, it will not try to release on the
+   * database a savepoint that no longer exists.
+   *
+   * Differently, rollbacks are strictly being checked for LIFO order: if a
+   * rollback is requested against a savepoint that is not the last created,
+   * the manager will throw a TransactionOutOfOrderException.
+   *
+   * The array key is the transaction's unique id, its value a StackItem.
+   *
+   * @var array<string,StackItem>
+   *
+   * @todo in https://www.drupal.org/project/drupal/issues/3384995, complete
+   *   the LIFO logic extending it to the root transaction too.
+   */
+  private array $stack = [];
+
+  /**
+   * A list of post-transaction callbacks.
+   *
+   * @var callable[]
+   */
+  private array $postTransactionCallbacks = [];
+
+  /**
+   * The state of the underlying client connection transaction.
+   *
+   * Note that this is a proxy of the actual state on the database server,
+   * best determined through calls to methods in this class. The actual
+   * state on the database server could be different.
+   */
+  private ClientConnectionTransactionState $connectionTransactionState;
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection.
+   */
+  public function __construct(
+    protected readonly Connection $connection,
+  ) {
+  }
+
+  /**
+   * Returns the current depth of the transaction stack.
+   *
+   * @return int
+   *   The current depth of the transaction stack.
+   *
+   * @todo consider making this function protected.
+   *
+   * @internal
+   */
+  public function stackDepth(): int {
+    return count($this->stack());
+  }
+
+  /**
+   * Returns the content of the transaction stack.
+   *
+   * Drivers should not override this method unless they also override the
+   * $stack property.
+   *
+   * phpcs:ignore Drupal.Commenting.FunctionComment.InvalidReturn
+   * @return array<string,StackItem>
+   *   The elements of the transaction stack.
+   */
+  protected function stack(): array {
+    return $this->stack;
+  }
+
+  /**
+   * Resets the transaction stack.
+   *
+   * Drivers should not override this method unless they also override the
+   * $stack property.
+   */
+  protected function resetStack(): void {
+    $this->stack = [];
+  }
+
+  /**
+   * Adds an item to the transaction stack.
+   *
+   * Drivers should not override this method unless they also override the
+   * $stack property.
+   *
+   * @param string $id
+   *   The id of the transaction.
+   * @param \Drupal\Core\Database\Transaction\StackItem $item
+   *   The stack item.
+   */
+  protected function addStackItem(string $id, StackItem $item): void {
+    $this->stack[$id] = $item;
+  }
+
+  /**
+   * Removes an item from the transaction stack.
+   *
+   * Drivers should not override this method unless they also override the
+   * $stack property.
+   *
+   * @param string $id
+   *   The id of the transaction.
+   */
+  protected function removeStackItem(string $id): void {
+    unset($this->stack[$id]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function inTransaction(): bool {
+    return (bool) $this->stackDepth() && $this->getConnectionTransactionState() === ClientConnectionTransactionState::Active;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function push(string $name = ''): Transaction {
+    if (!$this->inTransaction()) {
+      // If there is no transaction active, name the transaction
+      // 'drupal_transaction'.
+      $name = 'drupal_transaction';
+    }
+    elseif (!$name) {
+      // Within transactions, savepoints are used. Each savepoint requires a
+      // name. So if no name is present we need to create one.
+      $name = 'savepoint_' . $this->stackDepth();
+    }
+
+    if ($this->has($name)) {
+      throw new TransactionNameNonUniqueException($name . " is already in use.");
+    }
+
+    // Do the client-level processing.
+    if ($this->stackDepth() === 0) {
+      $this->beginClientTransaction();
+      $type = StackItemType::Root;
+      $this->setConnectionTransactionState(ClientConnectionTransactionState::Active);
+    }
+    else {
+      // If we're already in a Drupal transaction then we want to create a
+      // database savepoint, rather than try to begin another database
+      // transaction.
+      $this->addClientSavepoint($name);
+      $type = StackItemType::Savepoint;
+    }
+
+    // Define an unique id for the transaction.
+    $id = uniqid('', TRUE);
+
+    // Add an item on the stack, increasing its depth.
+    $this->addStackItem($id, new StackItem($name, $type));
+
+    // Actually return a new Transaction object.
+    return new Transaction($this->connection, $name, $id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function unpile(string $name, string $id): void {
+    // If the $id does not correspond to the one in the stack for that $name,
+    // we are facing an orphaned Transaction object (for example in case of a
+    // DDL statement breaking an active transaction), so there is nothing more
+    // to do.
+    if (!isset($this->stack()[$id]) || $this->stack()[$id]->name !== $name) {
+      return;
+    }
+
+    // If unpiling a savepoint, but that does not exist on the stack, the stack
+    // got corrupted.
+    if ($name !== 'drupal_transaction' && !$this->has($name)) {
+      throw new TransactionOutOfOrderException();
+    }
+
+    // Release the client transaction savepoint in case the Drupal transaction
+    // is not a root one.
+    if (
+      $this->has($name)
+      && $this->stack()[$id]->type === StackItemType::Savepoint
+      && $this->getConnectionTransactionState() === ClientConnectionTransactionState::Active
+    ) {
+      // If we are not releasing the last savepoint but an earlier one, all
+      // subsequent savepoints will have been released as well. The stack must
+      // be diminished accordingly.
+      while (($i = array_key_last($this->stack())) != $id) {
+        $this->removeStackItem((string) $i);
+      }
+      $this->releaseClientSavepoint($name);
+    }
+
+    // Remove the transaction from the stack.
+    $this->removeStackItem($id);
+
+    // If this was the last Drupal transaction open, we can commit the client
+    // transaction.
+    if (
+      $this->stackDepth() === 0
+      && $this->getConnectionTransactionState() === ClientConnectionTransactionState::Active
+    ) {
+      $this->processRootCommit();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rollback(string $name, string $id): void {
+    // @todo remove in drupal:11.0.0.
+    // Start of BC layer.
+    if ($id === 'bc-force-rollback') {
+      foreach ($this->stack() as $stackId => $item) {
+        if ($item->name === $name) {
+          $id = $stackId;
+          break;
+        }
+      }
+      if ($id === 'bc-force-rollback') {
+        throw new TransactionOutOfOrderException();
+      }
+    }
+    // End of BC layer.
+
+    // If the $id does not correspond to the one in the stack for that $name,
+    // we are facing an orphaned Transaction object (for example in case of a
+    // DDL statement breaking an active transaction), so there is nothing more
+    // to do.
+    if (!isset($this->stack()[$id]) || $this->stack()[$id]->name !== $name) {
+      return;
+    }
+
+    if (!$this->inTransaction()) {
+      throw new TransactionNoActiveException();
+    }
+
+    // Do the client-level processing.
+    match ($this->stack()[$id]->type) {
+      StackItemType::Root => $this->processRootRollback(),
+      StackItemType::Savepoint => $this->rollbackClientSavepoint($name),
+    };
+
+    // Rolled back item should match the last one in stack.
+    if ($id != array_key_last($this->stack())) {
+      throw new TransactionOutOfOrderException();
+    }
+
+    // Remove the transaction from the stack.
+    $this->removeStackItem($id);
+
+    // If this was the last Drupal transaction open, we can commit the client
+    // transaction.
+    if ($this->stackDepth() === 0 && $this->getConnectionTransactionState() === ClientConnectionTransactionState::Active) {
+      $this->processRootCommit();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addPostTransactionCallback(callable $callback): void {
+    if (!$this->inTransaction()) {
+      throw new \LogicException('Root transaction end callbacks can only be added when there is an active transaction.');
+    }
+    $this->postTransactionCallbacks[] = $callback;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function has(string $name): bool {
+    foreach ($this->stack() as $item) {
+      if ($item->name === $name) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Sets the state of the client connection transaction.
+   *
+   * Note that this is a proxy of the actual state on the database server,
+   * best determined through calls to methods in this class. The actual
+   * state on the database server could be different.
+   *
+   * Drivers should not override this method unless they also override the
+   * $connectionTransactionState property.
+   *
+   * @param \Drupal\Core\Database\Transaction\ClientConnectionTransactionState $state
+   *   The state of the client connection.
+   */
+  protected function setConnectionTransactionState(ClientConnectionTransactionState $state): void {
+    $this->connectionTransactionState = $state;
+  }
+
+  /**
+   * Gets the state of the client connection transaction.
+   *
+   * Note that this is a proxy of the actual state on the database server,
+   * best determined through calls to methods in this class. The actual
+   * state on the database server could be different.
+   *
+   * Drivers should not override this method unless they also override the
+   * $connectionTransactionState property.
+   *
+   * @return \Drupal\Core\Database\Transaction\ClientConnectionTransactionState
+   *   The state of the client connection.
+   */
+  protected function getConnectionTransactionState(): ClientConnectionTransactionState {
+    return $this->connectionTransactionState;
+  }
+
+  /**
+   * Processes the root transaction rollback.
+   */
+  protected function processRootRollback(): void {
+    $this->processPostTransactionCallbacks();
+    $this->rollbackClientTransaction();
+  }
+
+  /**
+   * Processes the root transaction commit.
+   *
+   * @throws \Drupal\Core\Database\TransactionCommitFailedException
+   *   If the commit of the root transaction failed.
+   */
+  protected function processRootCommit(): void {
+    $clientCommit = $this->commitClientTransaction();
+    $this->processPostTransactionCallbacks();
+    if (!$clientCommit) {
+      throw new TransactionCommitFailedException();
+    }
+  }
+
+  /**
+   * Processes the post-transaction callbacks.
+   */
+  protected function processPostTransactionCallbacks(): void {
+    if (!empty($this->postTransactionCallbacks)) {
+      $callbacks = $this->postTransactionCallbacks;
+      $this->postTransactionCallbacks = [];
+      foreach ($callbacks as $callback) {
+        call_user_func($callback, $this->getConnectionTransactionState() === ClientConnectionTransactionState::Committed || $this->getConnectionTransactionState() === ClientConnectionTransactionState::Voided);
+      }
+    }
+  }
+
+  /**
+   * Begins a transaction on the client connection.
+   *
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure.
+   */
+  abstract protected function beginClientTransaction(): bool;
+
+  /**
+   * Adds a savepoint on the client transaction.
+   *
+   * This is a generic implementation. Drivers should override this method
+   * to use a method specific for their client connection.
+   *
+   * @param string $name
+   *   The name of the savepoint.
+   *
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure.
+   */
+  protected function addClientSavepoint(string $name): bool {
+    $this->connection->query('SAVEPOINT ' . $name);
+    return TRUE;
+  }
+
+  /**
+   * Rolls back to a savepoint on the client transaction.
+   *
+   * This is a generic implementation. Drivers should override this method
+   * to use a method specific for their client connection.
+   *
+   * @param string $name
+   *   The name of the savepoint.
+   *
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure.
+   */
+  protected function rollbackClientSavepoint(string $name): bool {
+    $this->connection->query('ROLLBACK TO SAVEPOINT ' . $name);
+    return TRUE;
+  }
+
+  /**
+   * Releases a savepoint on the client transaction.
+   *
+   * This is a generic implementation. Drivers should override this method
+   * to use a method specific for their client connection.
+   *
+   * @param string $name
+   *   The name of the savepoint.
+   *
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure.
+   */
+  protected function releaseClientSavepoint(string $name): bool {
+    $this->connection->query('RELEASE SAVEPOINT ' . $name);
+    return TRUE;
+  }
+
+  /**
+   * Rolls back a client transaction.
+   *
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure.
+   */
+  abstract protected function rollbackClientTransaction(): bool;
+
+  /**
+   * Commits a client transaction.
+   *
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure.
+   */
+  abstract protected function commitClientTransaction(): bool;
+
+}
diff --git a/core/lib/Drupal/Core/Database/Transaction/TransactionManagerInterface.php b/core/lib/Drupal/Core/Database/Transaction/TransactionManagerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..1bd0cd3e5fde55e56737746594436e710f113f24
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/Transaction/TransactionManagerInterface.php
@@ -0,0 +1,122 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Database\Transaction;
+
+use Drupal\Core\Database\Transaction;
+
+/**
+ * Interface for the database transaction manager classes.
+ */
+interface TransactionManagerInterface {
+
+  /**
+   * Determines if there is an active transaction open.
+   *
+   * @return bool
+   *   TRUE if we're currently in a transaction, FALSE otherwise.
+   */
+  public function inTransaction(): bool;
+
+  /**
+   * Checks if a named Drupal transaction is active.
+   *
+   * @param string $name
+   *   The name of the transaction.
+   *
+   * @return bool
+   *   TRUE if the transaction is active, FALSE otherwise.
+   */
+  public function has(string $name): bool;
+
+  /**
+   * Pushes a new Drupal transaction on the stack.
+   *
+   * This begins a client connection transaction if there is not one active,
+   * or adds a savepoint to the active one.
+   *
+   * This method should only be called internally by a database driver.
+   *
+   * @param string $name
+   *   (optional) The name of the savepoint.
+   *
+   * @return \Drupal\Core\Database\Transaction
+   *   A Transaction object.
+   *
+   * @throws \Drupal\Core\Database\TransactionNameNonUniqueException
+   *   If a Drupal Transaction with the specified name exists already.
+   */
+  public function push(string $name = ''): Transaction;
+
+  /**
+   * Removes a Drupal transaction from the stack.
+   *
+   * The unpiled item does not necessarily need to be the last on the stack.
+   * This method should only be called by a Transaction object going out of
+   * scope.
+   *
+   * This method should only be called internally by a database driver.
+   *
+   * @param string $name
+   *   The name of the transaction.
+   * @param string $id
+   *   The id of the transaction.
+   *
+   * @throws \Drupal\Core\Database\TransactionOutOfOrderException
+   *   If a Drupal Transaction with the specified name does not exist.
+   * @throws \Drupal\Core\Database\TransactionCommitFailedException
+   *   If the commit of the root transaction failed.
+   */
+  public function unpile(string $name, string $id): void;
+
+  /**
+   * Rolls back a Drupal transaction.
+   *
+   * Rollbacks for nested transactions need to occur in reverse order to the
+   * pushes to the stack. Rolling back the last active Drupal transaction leads
+   * to rolling back the client connection (or to committing it in the edge
+   * case when the root was unpiled earlier).
+   *
+   * This method should only be called internally by a database driver.
+   *
+   * @param string $name
+   *   The name of the transaction.
+   * @param string $id
+   *   The id of the transaction.
+   *
+   * @throws \Drupal\Core\Database\TransactionNoActiveException
+   *   If there is no active client connection.
+   * @throws \Drupal\Core\Database\TransactionOutOfOrderException
+   *   If the order of rollback is not in reverse sequence against the pushes
+   *   to the stack.
+   * @throws \Drupal\Core\Database\TransactionCommitFailedException
+   *   If the commit of the root transaction failed.
+   */
+  public function rollback(string $name, string $id): void;
+
+  /**
+   * Adds a root transaction end callback.
+   *
+   * These callbacks are invoked immediately after the client transaction has
+   * been committed or rolled back.
+   *
+   * It can for example be used to avoid deadlocks on write-heavy tables that
+   * do not need to be part of the transaction, like cache tag invalidations.
+   *
+   * Another use case is that services using alternative backends like Redis
+   * and Memcache cache implementations can replicate the transaction-behavior
+   * of the database cache backend and avoid race conditions.
+   *
+   * An argument is passed to the callbacks that indicates whether the
+   * transaction was successful or not.
+   *
+   * @param callable $callback
+   *   The callback to invoke.
+   *
+   * @throws \LogicException
+   *   When a callback addition is attempted but no transaction is active.
+   */
+  public function addPostTransactionCallback(callable $callback): void;
+
+}
diff --git a/core/lib/Drupal/Core/Datetime/DateFormatter.php b/core/lib/Drupal/Core/Datetime/DateFormatter.php
index 03fb791723f75baf01814df71ea313a6e978d9e3..0d1017cd951e9ff1e89dd9b200653937fe0fac81 100644
--- a/core/lib/Drupal/Core/Datetime/DateFormatter.php
+++ b/core/lib/Drupal/Core/Datetime/DateFormatter.php
@@ -60,19 +60,19 @@ class DateFormatter implements DateFormatterInterface {
    * Contains the different date interval units.
    *
    * This array is keyed by strings representing the unit (e.g.
-   * '1 year|@count years') and with the amount of values of the unit in
+   * '@count year|@count years') and with the amount of values of the unit in
    * seconds.
    *
    * @var array
    */
   protected $units = [
-    '1 year|@count years' => 31536000,
-    '1 month|@count months' => 2592000,
-    '1 week|@count weeks' => 604800,
-    '1 day|@count days' => 86400,
-    '1 hour|@count hours' => 3600,
-    '1 min|@count min' => 60,
-    '1 sec|@count sec' => 1,
+    '@count year|@count years' => 31536000,
+    '@count month|@count months' => 2592000,
+    '@count week|@count weeks' => 604800,
+    '@count day|@count days' => 86400,
+    '@count hour|@count hours' => 3600,
+    '@count min|@count min' => 60,
+    '@count sec|@count sec' => 1,
   ];
 
   /**
@@ -156,7 +156,8 @@ public function formatInterval($interval, $granularity = 2, $langcode = NULL) {
       }
       elseif ($output) {
         // Break if there was previous output but not any output at this level,
-        // to avoid skipping levels and getting output like "1 year 1 second".
+        // to avoid skipping levels and getting output like "@count year @count
+        // second".
         break;
       }
 
@@ -237,12 +238,12 @@ public function formatDiff($from, $to, $options = []) {
         // strings for all different possibilities.
         switch ($value) {
           case 'y':
-            $interval_output = $this->formatPlural($interval->y, '1 year', '@count years', [], ['langcode' => $options['langcode']]);
+            $interval_output = $this->formatPlural($interval->y, '@count year', '@count years', [], ['langcode' => $options['langcode']]);
             $max_age = min($max_age, 365 * 86400);
             break;
 
           case 'm':
-            $interval_output = $this->formatPlural($interval->m, '1 month', '@count months', [], ['langcode' => $options['langcode']]);
+            $interval_output = $this->formatPlural($interval->m, '@count month', '@count months', [], ['langcode' => $options['langcode']]);
             $max_age = min($max_age, 30 * 86400);
             break;
 
@@ -253,35 +254,35 @@ public function formatDiff($from, $to, $options = []) {
             $days = $interval->d;
             $weeks = floor($days / 7);
             if ($weeks) {
-              $interval_output .= $this->formatPlural($weeks, '1 week', '@count weeks', [], ['langcode' => $options['langcode']]);
+              $interval_output .= $this->formatPlural($weeks, '@count week', '@count weeks', [], ['langcode' => $options['langcode']]);
               $days -= $weeks * 7;
               $granularity--;
               $max_age = min($max_age, 7 * 86400);
             }
 
             if ((!$output || $weeks > 0) && $granularity > 0 && $days > 0) {
-              $interval_output .= ($interval_output ? ' ' : '') . $this->formatPlural($days, '1 day', '@count days', [], ['langcode' => $options['langcode']]);
+              $interval_output .= ($interval_output ? ' ' : '') . $this->formatPlural($days, '@count day', '@count days', [], ['langcode' => $options['langcode']]);
               $max_age = min($max_age, 86400);
             }
             else {
               // If we did not output days, set the granularity to 0 so that we
-              // will not output hours and get things like "1 week 1 hour".
+              // will not output hours and get things like "@count week @count hour".
               $granularity = 0;
             }
             break;
 
           case 'h':
-            $interval_output = $this->formatPlural($interval->h, '1 hour', '@count hours', [], ['langcode' => $options['langcode']]);
+            $interval_output = $this->formatPlural($interval->h, '@count hour', '@count hours', [], ['langcode' => $options['langcode']]);
             $max_age = min($max_age, 3600);
             break;
 
           case 'i':
-            $interval_output = $this->formatPlural($interval->i, '1 minute', '@count minutes', [], ['langcode' => $options['langcode']]);
+            $interval_output = $this->formatPlural($interval->i, '@count minute', '@count minutes', [], ['langcode' => $options['langcode']]);
             $max_age = min($max_age, 60);
             break;
 
           case 's':
-            $interval_output = $this->formatPlural($interval->s, '1 second', '@count seconds', [], ['langcode' => $options['langcode']]);
+            $interval_output = $this->formatPlural($interval->s, '@count second', '@count seconds', [], ['langcode' => $options['langcode']]);
             $max_age = min($max_age, 1);
             break;
 
@@ -291,7 +292,7 @@ public function formatDiff($from, $to, $options = []) {
       }
       elseif ($output) {
         // Break if there was previous output but not any output at this level,
-        // to avoid skipping levels and getting output like "1 year 1 second".
+        // to avoid skipping levels and getting output like "@count year @count second".
         break;
       }
 
diff --git a/core/lib/Drupal/Core/Datetime/Element/Datetime.php b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
index 9a83720565874c40fe994e670be07a851243ec48..2eaf15d57bad5dff78e982392b82ea8ca363a728 100644
--- a/core/lib/Drupal/Core/Datetime/Element/Datetime.php
+++ b/core/lib/Drupal/Core/Datetime/Element/Datetime.php
@@ -239,7 +239,6 @@ public static function processDatetime(&$element, FormStateInterface $form_state
       // placeholders are invalid for HTML5 date and datetime, so an example
       // format is appended to the title to appear in tooltips.
       $extra_attributes = [
-        'title' => t('Date (e.g. @format)', ['@format' => static::formatExample($date_format)]),
         'type' => $element['#date_date_element'],
       ];
 
@@ -285,7 +284,6 @@ public static function processDatetime(&$element, FormStateInterface $form_state
 
       // Adds the HTML5 attributes.
       $extra_attributes = [
-        'title' => t('Time (e.g. @format)', ['@format' => static::formatExample($time_format)]),
         'type' => $element['#date_time_element'],
         'step' => $element['#date_increment'],
       ];
@@ -352,9 +350,6 @@ public static function validateDatetime(&$element, FormStateInterface $form_stat
     if ($input_exists) {
 
       $title = static::getElementTitle($element, $complete_form);
-      $date_format = $element['#date_date_element'] != 'none' ? static::getHtml5DateFormat($element) : '';
-      $time_format = $element['#date_time_element'] != 'none' ? static::getHtml5TimeFormat($element) : '';
-      $format = trim($date_format . ' ' . $time_format);
 
       // If there's empty input and the field is not required, set it to empty.
       if (empty($input['date']) && empty($input['time']) && !$element['#required']) {
@@ -363,7 +358,7 @@ public static function validateDatetime(&$element, FormStateInterface $form_stat
       // If there's empty input and the field is required, set an error. A
       // reminder of the required format in the message provides a good UX.
       elseif (empty($input['date']) && empty($input['time']) && $element['#required']) {
-        $form_state->setError($element, t('The %field date is required. Enter a date in the format %format.', ['%field' => $title, '%format' => static::formatExample($format)]));
+        $form_state->setError($element, t('The %field date is required.', ['%field' => $title]));
       }
       else {
         // If the date is valid, set it.
@@ -374,7 +369,7 @@ public static function validateDatetime(&$element, FormStateInterface $form_stat
         // If the date is invalid, set an error. A reminder of the required
         // format in the message provides a good UX.
         else {
-          $form_state->setError($element, t('The %field date is invalid. Enter a date in the format %format.', ['%field' => $title, '%format' => static::formatExample($format)]));
+          $form_state->setError($element, t('The %field date is invalid. Enter a date in the correct format.', ['%field' => $title]));
         }
       }
     }
@@ -389,8 +384,14 @@ public static function validateDatetime(&$element, FormStateInterface $form_stat
    *   The date format.
    *
    * @return string
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0.
+   *   There is no replacement.
+   *
+   * @see https://www.drupal.org/node/3385058
    */
   public static function formatExample($format) {
+    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3385058', E_USER_DEPRECATED);
     if (!static::$dateExample) {
       static::$dateExample = new DrupalDateTime();
     }
diff --git a/core/lib/Drupal/Core/Datetime/Plugin/Field/FieldWidget/TimestampDatetimeWidget.php b/core/lib/Drupal/Core/Datetime/Plugin/Field/FieldWidget/TimestampDatetimeWidget.php
index 0ebe7b52d5c9236911e4daeaf77579d7f22037a5..af3fd0dcee2135e3e8a4d1d2c797379f85ed6621 100644
--- a/core/lib/Drupal/Core/Datetime/Plugin/Field/FieldWidget/TimestampDatetimeWidget.php
+++ b/core/lib/Drupal/Core/Datetime/Plugin/Field/FieldWidget/TimestampDatetimeWidget.php
@@ -3,8 +3,6 @@
 namespace Drupal\Core\Datetime\Plugin\Field\FieldWidget;
 
 use Drupal\Core\Datetime\DrupalDateTime;
-use Drupal\Core\Datetime\Element\Datetime;
-use Drupal\Core\Datetime\Entity\DateFormat;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\WidgetBase;
 use Drupal\Core\Form\FormStateInterface;
@@ -27,9 +25,8 @@ class TimestampDatetimeWidget extends WidgetBase {
    * {@inheritdoc}
    */
   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
-    $date_format = DateFormat::load('html_date')->getPattern();
-    $time_format = DateFormat::load('html_time')->getPattern();
-    $default_value = isset($items[$delta]->value) ? DrupalDateTime::createFromTimestamp($items[$delta]->value) : '';
+    $parent_entity = $items->getParent()->getValue();
+    $default_value = (!$parent_entity->isNew() && isset($items[$delta]->value)) ? DrupalDateTime::createFromTimestamp($items[$delta]->value) : '';
     $element['value'] = $element + [
       '#type' => 'datetime',
       '#default_value' => $default_value,
@@ -38,9 +35,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
 
     $element['value']['#description'] = $element['#description'] !== ''
     ? $element['#description']
-    : $this->t('Format: %format. Leave blank to use the time of form submission.',
-    ['%format' => Datetime::formatExample($date_format . ' ' . $time_format)]);
-
+    : $this->t('Leave blank to use the time of form submission.');
     return $element;
   }
 
diff --git a/core/lib/Drupal/Core/DependencyInjection/DeprecatedServicePropertyTrait.php b/core/lib/Drupal/Core/DependencyInjection/DeprecatedServicePropertyTrait.php
index 52a4f70927e717ec25ab3b4667414995c1fa7c20..8f70555323ca10c02a68c65751067868b7b6969b 100644
--- a/core/lib/Drupal/Core/DependencyInjection/DeprecatedServicePropertyTrait.php
+++ b/core/lib/Drupal/Core/DependencyInjection/DeprecatedServicePropertyTrait.php
@@ -20,6 +20,7 @@ public function __get($name) {
     if (isset($this->deprecatedProperties[$name])) {
       $service_name = $this->deprecatedProperties[$name];
       $class_name = static::class;
+      // phpcs:ignore Drupal.Semantics.FunctionTriggerError
       @trigger_error("The property $name ($service_name service) is deprecated in $class_name and will be removed before Drupal 11.0.0.", E_USER_DEPRECATED);
       return \Drupal::service($service_name);
     }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php
index 9b35a84bef192bb64fee7bb7d3c4dbfc53fd70d5..0f42aafc6030e6ee7e839be9583d3f0ed23df687 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageInterface.php
@@ -5,7 +5,7 @@
 /**
  * A storage that supports content entity types.
  */
-interface ContentEntityStorageInterface extends EntityStorageInterface, TranslatableRevisionableStorageInterface {
+interface ContentEntityStorageInterface extends TranslatableRevisionableStorageInterface {
 
   /**
    * Creates an entity with sample field values.
diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
index cf660ab0cbc9981fb7e36aa3c9739696f1a224ea..33c2c3ed427f7d04fa6d5668edf83a70fc3b9fec 100644
--- a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
+++ b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php
@@ -191,7 +191,7 @@ public static function trustedCallbacks() {
    * @see https://www.drupal.org/node/3314346
    */
   public function viewRevision(EntityInterface $_entity_revision, $view_mode = 'full') {
-    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use \Drupal\Core\Entity\Controller\EntityRevisionViewController instead. See https://www.drupal.org/node/3314346.', E_USER_DEPRECATED);
+    @trigger_error(__METHOD__ . ' is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use \Drupal\Core\Entity\Controller\EntityRevisionViewController instead. See https://www.drupal.org/node/3314346', E_USER_DEPRECATED);
     return $this->view($_entity_revision, $view_mode);
   }
 
diff --git a/core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php b/core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php
index 8d069e5aeabcb8a78eb67460fec2c7e249c029a1..ef596202dec2b89e2da938f4fd0824dd9b40a9c7 100644
--- a/core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php
+++ b/core/lib/Drupal/Core/Entity/Controller/VersionHistoryController.php
@@ -25,6 +25,8 @@
  */
 class VersionHistoryController extends ControllerBase {
 
+  const REVISIONS_PER_PAGE = 50;
+
   /**
    * Constructs a new VersionHistoryController.
    *
@@ -213,6 +215,7 @@ protected function loadRevisions(RevisionableInterface $entity) {
       ->allRevisions()
       ->condition($entityType->getKey('id'), $entity->id())
       ->sort($entityType->getKey('revision'), 'DESC')
+      ->pager(self::REVISIONS_PER_PAGE)
       ->execute();
 
     $currentLangcode = $this->languageManager
@@ -222,7 +225,7 @@ protected function loadRevisions(RevisionableInterface $entity) {
       // Only show revisions that are affected by the language that is being
       // displayed.
       if (!$translatable || ($revision->hasTranslation($currentLangcode) && $revision->getTranslation($currentLangcode)->isRevisionTranslationAffected())) {
-        yield $revision;
+        yield ($translatable ? $revision->getTranslation($currentLangcode) : $revision);
       }
     }
   }
@@ -249,6 +252,8 @@ protected function revisionOverview(RevisionableInterface $entity): array {
       $build['entity_revisions_table']['#rows'][$revision->getRevisionId()] = $this->buildRow($revision);
     }
 
+    $build['pager'] = ['#type' => 'pager'];
+
     (new CacheableMetadata())
       // Only dealing with this entity and no external dependencies.
       ->addCacheableDependency($entity)
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
index 2384062319e6c994f54b17747ac416aeee6b4932..8160195a31b7863f8073e1bc204ac17497ed4dee 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormDisplay.php
@@ -35,6 +35,9 @@
  *     "mode",
  *     "content",
  *     "hidden",
+ *   },
+ *   constraints = {
+ *     "ImmutableProperties" = {"id", "targetEntityType", "bundle", "mode"},
  *   }
  * )
  */
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityFormMode.php b/core/lib/Drupal/Core/Entity/Entity/EntityFormMode.php
index 3a57641d66f045777c5c40c292d54449147d21e7..855e0a1d372c70572a82b004299fe3f945a8e093 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityFormMode.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityFormMode.php
@@ -36,6 +36,9 @@
  *     "description",
  *     "targetEntityType",
  *     "cache",
+ *   },
+ *   constraints = {
+ *     "ImmutableProperties" = {"id", "targetEntityType"},
  *   }
  * )
  */
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
index 0124b0f5b5c07c144f29c9d48feefcc37c9a1c78..19cb4219b89a2ebc741fa2aafa0672baa05ac3ee 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewDisplay.php
@@ -34,6 +34,9 @@
  *     "mode",
  *     "content",
  *     "hidden",
+ *   },
+ *   constraints = {
+ *     "ImmutableProperties" = {"id", "targetEntityType", "bundle", "mode"},
  *   }
  * )
  */
diff --git a/core/lib/Drupal/Core/Entity/Entity/EntityViewMode.php b/core/lib/Drupal/Core/Entity/Entity/EntityViewMode.php
index 21bcd8888f95f328379b0c2341e89b12a649c7f8..1d54022454dab98368b1e26bad2c7adb66aa6292 100644
--- a/core/lib/Drupal/Core/Entity/Entity/EntityViewMode.php
+++ b/core/lib/Drupal/Core/Entity/Entity/EntityViewMode.php
@@ -38,6 +38,9 @@
  *     "description",
  *     "targetEntityType",
  *     "cache",
+ *   },
+ *   constraints = {
+ *     "ImmutableProperties" = {"id", "targetEntityType"},
  *   }
  * )
  */
diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControlHandlerInterface.php b/core/lib/Drupal/Core/Entity/EntityAccessControlHandlerInterface.php
index 5532996b2d993cb54fde2b8c7f2b3a9a75094f9e..92e09562d54f96abf3c2b4111d9136b0f931d39f 100644
--- a/core/lib/Drupal/Core/Entity/EntityAccessControlHandlerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityAccessControlHandlerInterface.php
@@ -86,8 +86,9 @@ public function setModuleHandler(ModuleHandlerInterface $module_handler);
    * \Drupal\Core\Entity\EntityAccessControlHandlerInterface::access().
    *
    * @param string $operation
-   *   The operation access should be checked for.
-   *   Usually one of "view" or "edit".
+   *   The operation access should be checked for. Usually one of "view" or
+   *   "edit". Unlike entity access, for field access there is no distinction
+   *   between creating and updating.
    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
    *   The field definition.
    * @param \Drupal\Core\Session\AccountInterface $account
diff --git a/core/lib/Drupal/Core/Entity/EntityBase.php b/core/lib/Drupal/Core/Entity/EntityBase.php
index a42ef483366fe86768bafa54d50ed3b32158d6a2..79e61b3e14a461b0db9aeadea816806afc9b21b1 100644
--- a/core/lib/Drupal/Core/Entity/EntityBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityBase.php
@@ -156,7 +156,7 @@ public function label() {
   /**
    * {@inheritdoc}
    */
-  public function toUrl($rel = 'canonical', array $options = []) {
+  public function toUrl($rel = NULL, array $options = []) {
     if ($this->id() === NULL) {
       throw new EntityMalformedException(sprintf('The "%s" entity cannot have a URI as it does not have an ID', $this->getEntityTypeId()));
     }
@@ -164,6 +164,19 @@ public function toUrl($rel = 'canonical', array $options = []) {
     // The links array might contain URI templates set in annotations.
     $link_templates = $this->linkTemplates();
 
+    // Use the canonical link template by default, or edit-form if there is not
+    // a canonical one.
+    if ($rel === NULL) {
+      $rel_candidates = array_intersect(
+        ['canonical', 'edit-form'],
+        array_flip($link_templates),
+      );
+      $rel = array_shift($rel_candidates);
+      if ($rel === NULL) {
+        throw new UndefinedLinkTemplateException("Cannot generate default URL because no link template 'canonical' or 'edit-form' was found for the '{$this->getEntityTypeId()}' entity type");
+      }
+    }
+
     // Links pointing to the current revision point to the actual entity. So
     // instead of using the 'revision' link, use the 'canonical' link.
     if ($rel === 'revision' && $this instanceof RevisionableInterface && $this->isDefaultRevision()) {
diff --git a/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php
index cdb70747ef5ff753fb16ab52063b007c89191906..dc5007a3b528d7652a6a23208971bdbb18af8671 100644
--- a/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php
+++ b/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php
@@ -23,7 +23,7 @@ class EntityDeleteMultipleAccessCheck implements AccessInterface {
   /**
    * The tempstore service.
    *
-   * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
+   * @var \Drupal\Core\TempStore\PrivateTempStore
    */
   protected $tempStore;
 
@@ -40,7 +40,7 @@ class EntityDeleteMultipleAccessCheck implements AccessInterface {
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager.
    * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
-   *   The tempstore service.
+   *   The tempstore factory service.
    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
    *   The request stack service.
    */
diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php
index 90d3d635ff0c69fa440e3e451947228a3467a722..18a70a62dfb78b92f5c7163e5686c417fe5b2b6a 100644
--- a/core/lib/Drupal/Core/Entity/EntityForm.php
+++ b/core/lib/Drupal/Core/Entity/EntityForm.php
@@ -323,6 +323,8 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
    *   A nested array of form elements comprising the form.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
+   *
+   * @see \Drupal\Core\Form\ConfigFormBase::copyFormValuesToConfig()
    */
   protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
     $values = $form_state->getValues();
diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index be04a8b97e3825584f1e7de0b8b57cf20ec3d66f..b154eca3b25584764364bee18795b3bc3362bc59 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -120,7 +120,9 @@ public function label();
    * entity/entityType/id.
    *
    * @param string $rel
-   *   The link relationship type, for example: canonical or edit-form.
+   *   The link relationship type, for example: canonical or edit-form. If none
+   *   is provided, canonical is assumed, or edit-form if no canonical link
+   *   exists.
    * @param array $options
    *   See \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() for
    *   the available options.
@@ -131,7 +133,7 @@ public function label();
    * @throws \Drupal\Core\Entity\EntityMalformedException
    * @throws \Drupal\Core\Entity\Exception\UndefinedLinkTemplateException
    */
-  public function toUrl($rel = 'canonical', array $options = []);
+  public function toUrl($rel = NULL, array $options = []);
 
   /**
    * Generates the HTML for a link to this entity.
diff --git a/core/lib/Drupal/Core/Entity/Form/RevisionDeleteForm.php b/core/lib/Drupal/Core/Entity/Form/RevisionDeleteForm.php
index 40b73e2cfaf657b8bcb9b3cadcca963bce7ee94d..f38775a2dd2c0ef798a9032ee7204af0ac9ee73b 100644
--- a/core/lib/Drupal/Core/Entity/Form/RevisionDeleteForm.php
+++ b/core/lib/Drupal/Core/Entity/Form/RevisionDeleteForm.php
@@ -140,6 +140,7 @@ public function getDescription() {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $entityTypeId = $this->revision->getEntityTypeId();
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entityStorage */
     $entityStorage = $this->entityTypeManager->getStorage($entityTypeId);
     $entityStorage->deleteRevision($this->revision->getRevisionId());
 
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php
index 730e5390cb5259dd1b14f22e93a19e797f6e67e0..56f0da64fe8c6c0b2c7fd1a56b48ddad3ec69186 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php
@@ -100,9 +100,10 @@ protected function hasUntranslatableFieldsChanges(ContentEntityInterface $entity
       $original = $entity->original;
     }
     else {
-      $original = $this->entityTypeManager
-        ->getStorage($entity->getEntityTypeId())
-        ->loadRevision($entity->getLoadedRevisionId());
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+      $storage = $this->entityTypeManager
+        ->getStorage($entity->getEntityTypeId());
+      $original = $storage->loadRevision($entity->getLoadedRevisionId());
     }
 
     foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ImmutablePropertiesConstraint.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ImmutablePropertiesConstraint.php
new file mode 100644
index 0000000000000000000000000000000000000000..23202b38ef4a556371a924b9f8085a04b5af3805
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ImmutablePropertiesConstraint.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Checks if config entity properties have been changed.
+ *
+ * @Constraint(
+ *   id = "ImmutableProperties",
+ *   label = @Translation("Properties are unchanged", context = "Validation"),
+ *   type = { "entity" }
+ * )
+ */
+class ImmutablePropertiesConstraint extends Constraint {
+
+  /**
+   * The error message if an immutable property has been changed.
+   *
+   * @var string
+   */
+  public string $message = "The '@name' property cannot be changed.";
+
+  /**
+   * The names of the immutable properties.
+   *
+   * @var string[]
+   */
+  public array $properties = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultOption() {
+    return 'properties';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRequiredOptions() {
+    return ['properties'];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ImmutablePropertiesConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ImmutablePropertiesConstraintValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..b55842e1dcb8d95308916deb6afd51a4a01fbfd0
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ImmutablePropertiesConstraintValidator.php
@@ -0,0 +1,77 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\RuntimeException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * Validates the ImmutableProperties constraint.
+ */
+class ImmutablePropertiesConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  /**
+   * Constructs an ImmutablePropertiesConstraintValidator object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager service.
+   */
+  public function __construct(protected EntityTypeManagerInterface $entityTypeManager) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container): static {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate(mixed $value, Constraint $constraint) {
+    assert($constraint instanceof ImmutablePropertiesConstraint);
+
+    if (!$value instanceof ConfigEntityInterface) {
+      throw new UnexpectedValueException($value, ConfigEntityInterface::class);
+    }
+    // This validation is irrelevant on new entities.
+    if ($value->isNew()) {
+      return;
+    }
+
+    $id = $value->getOriginalId() ?: $value->id();
+    if (empty($id)) {
+      throw new LogicException('The entity does not have an ID.');
+    }
+
+    $original = $this->entityTypeManager->getStorage($value->getEntityTypeId())
+      ->loadUnchanged($id);
+    if (empty($original)) {
+      throw new RuntimeException('The original entity could not be loaded.');
+    }
+
+    foreach ($constraint->properties as $name) {
+      // The property must be concretely defined in the class.
+      if (!property_exists($value, $name)) {
+        throw new LogicException("The entity does not have a '$name' property.");
+      }
+
+      if ($original->get($name) !== $value->get($name)) {
+        $this->context->addViolation($constraint->message, ['@name' => $name]);
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php b/core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php
index d8a27386e3e9f6794d04e1b0b242b78ea57d99fe..3d34f9e56198a7b28f169f21cc12bf6fa7495917 100644
--- a/core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php
+++ b/core/lib/Drupal/Core/Entity/TranslatableRevisionableStorageInterface.php
@@ -5,7 +5,7 @@
 /**
  * A storage that supports translatable and revisionable entity types.
  */
-interface TranslatableRevisionableStorageInterface extends TranslatableStorageInterface, RevisionableStorageInterface {
+interface TranslatableRevisionableStorageInterface extends RevisionableStorageInterface, TranslatableStorageInterface {
 
   /**
    * Creates a new revision starting off from the specified entity object.
diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php
index 38a77553d6fa6319bd930f1687e47f3af42e7aeb..73c2f060dd640dcccd96217487019ff26f361f59 100644
--- a/core/lib/Drupal/Core/Entity/entity.api.php
+++ b/core/lib/Drupal/Core/Entity/entity.api.php
@@ -1390,7 +1390,7 @@ function hook_entity_predelete(\Drupal\Core\Entity\EntityInterface $entity) {
 
   // Log the count in a table that records this statistic for deleted entities.
   $connection->merge('example_deleted_entity_statistics')
-    ->key(['type' => $type, 'id' => $id])
+    ->keys(['type' => $type, 'id' => $id])
     ->fields(['count' => $count])
     ->execute();
 }
@@ -1419,7 +1419,7 @@ function hook_ENTITY_TYPE_predelete(\Drupal\Core\Entity\EntityInterface $entity)
 
   // Log the count in a table that records this statistic for deleted entities.
   $connection->merge('example_deleted_entity_statistics')
-    ->key(['type' => $type, 'id' => $id])
+    ->keys(['type' => $type, 'id' => $id])
     ->fields(['count' => $count])
     ->execute();
 }
diff --git a/core/lib/Drupal/Core/Extension/DatabaseDriver.php b/core/lib/Drupal/Core/Extension/DatabaseDriver.php
index 4e134c1a7a35b5c1d1297deb582b292776155b08..24785766d10aadf559f1e96b1c6cb1acafc5388a 100644
--- a/core/lib/Drupal/Core/Extension/DatabaseDriver.php
+++ b/core/lib/Drupal/Core/Extension/DatabaseDriver.php
@@ -207,7 +207,7 @@ public function isObsolete(): bool {
   private function getModuleInfo(): void {
     if (!isset($this->info)) {
       $infoParser = new InfoParser($this->root);
-      $this->info = $infoParser->parse($this->root . DIRECTORY_SEPARATOR . $this->getModule()->getPathname());
+      $this->info = $infoParser->parse($this->getModule()->getPathname());
     }
   }
 
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 51f5556e01ecd75340090c6fafd9b68361a658aa..4e5734bb6bcaa2849a635c60ad7a65606bdaac7e 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -126,6 +126,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
         throw new ObsoleteExtensionException("Unable to install modules: module '$module' is obsolete.");
       }
       if ($module_data[$module]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
+        // phpcs:ignore Drupal.Semantics.FunctionTriggerError
         @trigger_error("The module '$module' is deprecated. See " . $module_data[$module]->info['lifecycle_link'], E_USER_DEPRECATED);
       }
     }
diff --git a/core/lib/Drupal/Core/Extension/ThemeInstaller.php b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
index c58ac24ea29e7c5e80afe29d268fa31f8d16c966..e3b4f5057b8a54579ed92023256ac4d9e912a4c1 100644
--- a/core/lib/Drupal/Core/Extension/ThemeInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ThemeInstaller.php
@@ -159,6 +159,7 @@ public function install(array $theme_list, $install_dependencies = TRUE) {
         $unmet_module_dependencies = array_diff_key($module_dependencies, $installed_modules);
 
         if ($theme_data[$theme]->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
+          // phpcs:ignore Drupal.Semantics.FunctionTriggerError
           @trigger_error("The theme '$theme' is deprecated. See " . $theme_data[$theme]->info['lifecycle_link'], E_USER_DEPRECATED);
         }
 
diff --git a/core/lib/Drupal/Core/Field/Entity/BaseFieldOverride.php b/core/lib/Drupal/Core/Field/Entity/BaseFieldOverride.php
index 54c08284f4e1ec92e5f0c287a400205dd606d60b..334fe245ad669e49577d47db34a1e367c350c2ca 100644
--- a/core/lib/Drupal/Core/Field/Entity/BaseFieldOverride.php
+++ b/core/lib/Drupal/Core/Field/Entity/BaseFieldOverride.php
@@ -37,6 +37,9 @@
  *     "default_value_callback",
  *     "settings",
  *     "field_type",
+ *   },
+ *   constraints = {
+ *     "ImmutableProperties" = {"id", "entity_type", "bundle", "field_name", "field_type"},
  *   }
  * )
  */
diff --git a/core/lib/Drupal/Core/Field/FallbackFieldTypeCategory.php b/core/lib/Drupal/Core/Field/FallbackFieldTypeCategory.php
index 1a83aa0737403464b7340071d38e1468d9bbd32c..d8319d28d501ef47a34a18de757bbf4e285a309c 100644
--- a/core/lib/Drupal/Core/Field/FallbackFieldTypeCategory.php
+++ b/core/lib/Drupal/Core/Field/FallbackFieldTypeCategory.php
@@ -10,13 +10,14 @@ class FallbackFieldTypeCategory extends FieldTypeCategory {
   /**
    * {@inheritdoc}
    */
-  public function __construct(array $configuration) {
+  public function __construct(array $configuration, string $plugin_id, array $plugin_definition) {
+    $plugin_id = $configuration['unique_identifier'];
     $plugin_definition = [
       'label' => $configuration['label'] ?? '',
       'description' => $configuration['description'] ?? '',
       'weight' => $configuration['weight'] ?? 0,
-    ];
-    parent::__construct($configuration, $configuration['unique_identifier'], $plugin_definition);
+    ] + $plugin_definition;
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Field/FieldConfigBase.php b/core/lib/Drupal/Core/Field/FieldConfigBase.php
index b58f49dc6986b79596229b2dc808d71648b27247..e9032e8cc9f6708f4c2a62663e36fea952bc327e 100644
--- a/core/lib/Drupal/Core/Field/FieldConfigBase.php
+++ b/core/lib/Drupal/Core/Field/FieldConfigBase.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
+use Drupal\field\Entity\FieldStorageConfig;
 
 /**
  * Base class for configurable field definitions.
@@ -279,6 +280,13 @@ public function postCreate(EntityStorageInterface $storage) {
     if (empty($this->field_type)) {
       $this->field_type = $this->getFieldStorageDefinition()->getType();
     }
+
+    // Make sure all expected runtime settings are present.
+    $default_settings = \Drupal::service('plugin.manager.field.field_type')
+      ->getDefaultFieldSettings($this->getType());
+    // Filter out any unknown (unsupported) settings.
+    $supported_settings = array_intersect_key($this->getSettings(), $default_settings);
+    $this->set('settings', $supported_settings + $default_settings);
   }
 
   /**
@@ -461,10 +469,17 @@ public function setDefaultValueCallback($callback) {
    * @todo Investigate in https://www.drupal.org/node/1977206.
    */
   public function __sleep() {
+    $properties = get_object_vars($this);
+
     // Only serialize necessary properties, excluding those that can be
     // recalculated.
-    $properties = get_object_vars($this);
-    unset($properties['fieldStorage'], $properties['itemDefinition'], $properties['original']);
+    unset($properties['itemDefinition'], $properties['original']);
+
+    // Field storage can be recalculated if it's not new.
+    if (array_key_exists('fieldStorage', $properties) && $properties['fieldStorage'] instanceof FieldStorageConfig && !$properties['fieldStorage']->isNew()) {
+      unset($properties['fieldStorage']);
+    }
+
     return array_keys($properties);
   }
 
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index 33b3a37ef0280ed7f022be652ea07ee1d82a0bd0..c1d3b70edb99745777e53611067eaf9e154c9680 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -370,7 +370,17 @@ protected function defaultValueWidget(FormStateInterface $form_state) {
         ]);
       }
       else {
-        $widget = $field_widget_plugin_manager->getInstance(['field_definition' => $definition]);
+        $options = [
+          'field_definition' => $this->getFieldDefinition(),
+        ];
+        // If the field does not have a widget configured in the 'default' form
+        // mode, check if there are default entity form display options defined
+        // for the 'default' form mode in the form state.
+        // @see \Drupal\field_ui\Controller\FieldConfigAddController::fieldConfigAddConfigureForm
+        if (($default_options = $form_state->get('default_options')) && isset($default_options['entity_form_display']['default'])) {
+          $options['configuration'] = $default_options['entity_form_display']['default'];
+        }
+        $widget = $field_widget_plugin_manager->getInstance($options);
       }
 
       $form_state->set('default_value_widget', $widget);
diff --git a/core/lib/Drupal/Core/Field/FieldTypeCategory.php b/core/lib/Drupal/Core/Field/FieldTypeCategory.php
index ac04b5124947cc269ac3b1f481913d44a7dbce6f..168616102e1b48ee7dd32544db14cf48c9046cdb 100644
--- a/core/lib/Drupal/Core/Field/FieldTypeCategory.php
+++ b/core/lib/Drupal/Core/Field/FieldTypeCategory.php
@@ -33,4 +33,11 @@ public function getWeight(): int {
     return $this->pluginDefinition['weight'];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getLibraries(): array {
+    return $this->pluginDefinition['libraries'] ?? [];
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Field/FieldTypeCategoryInterface.php b/core/lib/Drupal/Core/Field/FieldTypeCategoryInterface.php
index e9a1a0d21a851cc61560d983159245cab975ecf6..3727df1bb778c45a58fa488588c49713547e7723 100644
--- a/core/lib/Drupal/Core/Field/FieldTypeCategoryInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldTypeCategoryInterface.php
@@ -33,4 +33,12 @@ public function getDescription(): TranslatableMarkup;
    */
   public function getWeight(): int;
 
+  /**
+   * Returns asset libraries for the field group.
+   *
+   * @return array
+   *   The asset libraries to attach.
+   */
+  public function getLibraries(): array;
+
 }
diff --git a/core/lib/Drupal/Core/Field/FieldTypeCategoryManager.php b/core/lib/Drupal/Core/Field/FieldTypeCategoryManager.php
index 29efa654063d7cf56fab4bbf8f94606cf01a7241..262c50da46a7b3c422783beb692ea30f5f89db8a 100644
--- a/core/lib/Drupal/Core/Field/FieldTypeCategoryManager.php
+++ b/core/lib/Drupal/Core/Field/FieldTypeCategoryManager.php
@@ -19,6 +19,8 @@
  *     label: STRING
  *     description: STRING
  *     weight: INTEGER
+ *     libraries:
+ *       - STRING
  * @endcode
  * For example:
  * @code
@@ -26,6 +28,8 @@
  *   label: Text
  *   description: Text fields.
  *   weight: 2
+ *   libraries:
+ *     - module_name/library_name
  * @endcode
  *
  * @see \Drupal\Core\Field\FieldTypeCategoryInterface
@@ -75,6 +79,17 @@ protected function getDiscovery(): YamlDiscovery {
     return $this->discovery;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function alterDefinitions(&$definitions): void {
+    parent::alterDefinitions($definitions);
+
+    if (!isset($definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY])) {
+      throw new \LogicException('Missing fallback category.');
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Form/ConfigFormBase.php b/core/lib/Drupal/Core/Form/ConfigFormBase.php
index 707e580d5a6d822251109bb131fc5fc6c6733cf8..eefc0ea5ba7d22637e01cbcb7876fcb681ff00de 100644
--- a/core/lib/Drupal/Core/Form/ConfigFormBase.php
+++ b/core/lib/Drupal/Core/Form/ConfigFormBase.php
@@ -2,11 +2,17 @@
 
 namespace Drupal\Core\Form;
 
+use Drupal\Core\Config\Config;
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Base class for implementing system configuration forms.
+ *
+ * Subclasses of this form can choose to use config validation instead of form-
+ * -specific validation logic. To do that, override copyFormValuesToConfig().
  */
 abstract class ConfigFormBase extends FormBase {
   use ConfigFormBaseTrait;
@@ -16,9 +22,18 @@ abstract class ConfigFormBase extends FormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface|null $typedConfigManager
+   *   The typed config manager.
    */
-  public function __construct(ConfigFactoryInterface $config_factory) {
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    protected ?TypedConfigManagerInterface $typedConfigManager = NULL,
+  ) {
     $this->setConfigFactory($config_factory);
+    if ($this->typedConfigManager === NULL) {
+      @trigger_error('Calling ConfigFormBase::__construct() without the $typedConfigManager argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3373502', E_USER_DEPRECATED);
+      $this->typedConfigManager = \Drupal::service('config.typed');
+    }
   }
 
   /**
@@ -26,7 +41,8 @@ public function __construct(ConfigFactoryInterface $config_factory) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('config.factory')
+      $container->get('config.factory'),
+      $container->get('config.typed')
     );
   }
 
@@ -47,11 +63,173 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     return $form;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    assert($this->typedConfigManager instanceof TypedConfigManagerInterface);
+    foreach ($this->getEditableConfigNames() as $config_name) {
+      $config = $this->config($config_name);
+      try {
+        static::copyFormValuesToConfig($config, $form_state);
+      }
+      catch (\BadMethodCallException $e) {
+        // Nothing to do: this config form does not yet use validation
+        // constraints. Continue trying the other editable config, to allow
+        // partial adoption.
+        continue;
+      }
+      $typed_config = $this->typedConfigManager->createFromNameAndData($config_name, $config->getRawData());
+
+      $violations = $typed_config->validate();
+      // Rather than immediately applying all violation messages to the
+      // corresponding form elements, first collect the messages. The structure
+      // of the form may cause a single form element to contain multiple config
+      // property paths thanks to `type: sequence`. Common example: a <textarea>
+      // with one line per sequence item.
+      // @see \Drupal\Core\Config\Schema\Sequence
+      // @see \Drupal\Core\Config\Schema\SequenceDataDefinition
+      $violations_per_form_element = [];
+      foreach ($violations as $violation) {
+        $property_path = $violation->getPropertyPath();
+        $form_element_name = static::mapConfigKeyToFormElementName($config_name, $property_path);
+        // Default to index 0.
+        $index = 0;
+        // Detect if this is a sequence property path, and if so, determine the
+        // actual sequence index.
+        $matches = [];
+        if (preg_match("/.*\.(\d+)$/", $property_path, $matches) === 1) {
+          $index = intval($matches[1]);
+        }
+        $violations_per_form_element[$form_element_name][$index] = $violation;
+      }
+
+      // Now that we know how many constraint violation messages exist per form
+      // element, set them. This is crucial because FormState::setErrorByName()
+      // only allows a single validation error message per form element.
+      // @see \Drupal\Core\Form\FormState::setErrorByName()
+      foreach ($violations_per_form_element as $form_element_name => $violations) {
+        // When only a single message exists, just set it.
+        if (count($violations) === 1) {
+          $form_state->setErrorByName($form_element_name, reset($violations)->getMessage());
+          continue;
+        }
+
+        // However, if multiple exist, that implies it's a single form element
+        // containing a `type: sequence`.
+        $form_state->setErrorByName($form_element_name, $this->formatMultipleViolationsMessage($form_element_name, $violations));
+      }
+    }
+  }
+
+  /**
+   * Formats multiple violation messages associated with a single form element.
+   *
+   * Validation constraints only know the internal data structure (the
+   * configuration schema structure), but this need not be a disadvantage:
+   * rather than informing the user some values are wrong, it is possible
+   * guide them directly to the Nth entry in the sequence.
+   *
+   * To further improve the user experience, it is possible to override
+   * method in subclasses to use specific knowledge about the structure of the
+   * form and the nature of the data being validated, to instead generate more
+   * precise and/or shortened violation messages.
+   *
+   * @param string $form_element_name
+   *   The form element for which to format multiple violation messages.
+   * @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations
+   *   The list of constraint violations that apply to this form element.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+   */
+  protected function formatMultipleViolationsMessage(string $form_element_name, array $violations): TranslatableMarkup {
+    $transformed_message_parts = [];
+    foreach ($violations as $index => $violation) {
+      // Note that `@validation_error_message` (should) already contain a
+      // trailing period, hence it is intentionally absent here.
+      $transformed_message_parts[] = $this->t('Entry @human_index: @validation_error_message', [
+        // Humans start counting from 1, not 0.
+        '@human_index' => $index + 1,
+        // Translators may not necessarily know what "violation constraint
+        // messages" are, but they definitely know "validation errors".
+        '@validation_error_message' => $violation->getMessage(),
+      ]);
+    }
+    return $this->t(implode("\n", $transformed_message_parts));
+  }
+
   /**
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    foreach ($this->getEditableConfigNames() as $config_name) {
+      $config = $this->config($config_name);
+      try {
+        static::copyFormValuesToConfig($config, $form_state);
+        $config->save();
+      }
+      catch (\BadMethodCallException $e) {
+        // Nothing to do: this config form does not yet use validation
+        // constraints. Continue trying the other editable config, to allow
+        // partial adoption.
+        continue;
+      }
+    }
     $this->messenger()->addStatus($this->t('The configuration options have been saved.'));
   }
 
+  /**
+   * Copies form values to Config keys.
+   *
+   * This should not change existing Config key-value pairs that are not being
+   * edited by this form.
+   *
+   * @param \Drupal\Core\Config\Config $config
+   *   The configuration being edited.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @see \Drupal\Core\Entity\EntityForm::copyFormValuesToEntity()
+   */
+  protected static function copyFormValuesToConfig(Config $config, FormStateInterface $form_state): void {
+    // This allows ::submitForm() and ::validateForm() to know that this config
+    // form is not yet using constraint-based validation.
+    throw new \BadMethodCallException();
+  }
+
+  /**
+   * Maps the given Config key to a form element name.
+   *
+   * @param string $config_name
+   *   The name of the Config whose value triggered a validation error.
+   * @param string $key
+   *   The Config key that triggered a validation error (which corresponds to a
+   *   property path on the validation constraint violation).
+   *
+   * @return string
+   *   The corresponding form element name.
+   */
+  protected static function mapConfigKeyToFormElementName(string $config_name, string $key) : string {
+    return self::defaultMapConfigKeyToFormElementName($config_name, $key);
+  }
+
+  /**
+   * Default implementation for ::mapConfigKeyToFormElementName().
+   *
+   * Suitable when the configuration is mapped 1:1 to form elements: when the
+   * keys in the Config match the form element names exactly.
+   *
+   * @param string $config_name
+   *   The name of the Config whose value triggered a validation error.
+   * @param string $key
+   *   The Config key that triggered a validation error (which corresponds to a
+   *   property path on the validation constraint violation).
+   *
+   * @return string
+   *   The corresponding form element name.
+   */
+  final protected static function defaultMapConfigKeyToFormElementName(string $config_name, string $key) : string {
+    return str_replace('.', '][', $key);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php b/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php
index 75b12bf30f3ffb9fdbd3e48413d0b735b87fedcb..92fa49541754635c737d15f4f3a87268ad23c225 100644
--- a/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php
+++ b/core/lib/Drupal/Core/Form/EventSubscriber/FormAjaxSubscriber.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Form\FormAjaxResponseBuilderInterface;
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -86,7 +87,9 @@ public function onException(ExceptionEvent $event) {
     // Render a nice error message in case we have a file upload which exceeds
     // the configured upload limit.
     if ($exception instanceof BrokenPostRequestException && $request->query->has(FormBuilderInterface::AJAX_FORM_REQUEST)) {
-      $this->messenger->addError($this->t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', ['@size' => $this->formatSize($exception->getSize())]));
+      $this->messenger->addError($this->t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', [
+        '@size' => ByteSizeMarkup::create((int) $exception->getSize()),
+      ]));
       $response = new AjaxResponse(NULL, 200);
       $status_messages = ['#type' => 'status_messages'];
       $response->addCommand(new PrependCommand(NULL, $status_messages));
@@ -148,8 +151,14 @@ protected function getFormAjaxException(\Throwable $e) {
    *
    * @return string
    *   The formatted size.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   \Drupal\Core\StringTranslation\ByteSizeMarkup::create() instead.
+   *
+   * @see https://www.drupal.org/node/2999981
    */
   protected function formatSize($size) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\StringTranslation\ByteSizeMarkup::create() instead. See https://www.drupal.org/node/2999981', E_USER_DEPRECATED);
     return format_size($size);
   }
 
diff --git a/core/lib/Drupal/Core/Http/ClientFactory.php b/core/lib/Drupal/Core/Http/ClientFactory.php
index e429bbb9cda4734f9c83db529e53afee50d4b3ca..950ebb9724a544aaaed083e646de71714dd4fba5 100644
--- a/core/lib/Drupal/Core/Http/ClientFactory.php
+++ b/core/lib/Drupal/Core/Http/ClientFactory.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Site\Settings;
 use GuzzleHttp\Client;
 use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Utils;
 
 /**
  * Helper class to construct a HTTP client with Drupal specific config.
@@ -49,7 +50,7 @@ public function fromOptions(array $config = []) {
       'verify' => TRUE,
       'timeout' => 30,
       'headers' => [
-        'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . \GuzzleHttp\default_user_agent(),
+        'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . Utils::defaultUserAgent(),
       ],
       'handler' => $this->stack,
       // Security consideration: prevent Guzzle from using environment variables
diff --git a/core/lib/Drupal/Core/Layout/LayoutPluginManager.php b/core/lib/Drupal/Core/Layout/LayoutPluginManager.php
index 4d211aaefdba75e67f22d72eece26251a095807d..2265194bf30bd2bd08b3c7ce8dadd2390ff047f4 100644
--- a/core/lib/Drupal/Core/Layout/LayoutPluginManager.php
+++ b/core/lib/Drupal/Core/Layout/LayoutPluginManager.php
@@ -199,9 +199,7 @@ public function getCategories() {
   public function getSortedDefinitions(array $definitions = NULL, $label_key = 'label') {
     // Sort the plugins first by category, then by label.
     $definitions = $definitions ?? $this->getDefinitions();
-    // Suppress errors because PHPUnit will indirectly modify the contents,
-    // triggering https://bugs.php.net/bug.php?id=50688.
-    @uasort($definitions, function (LayoutDefinition $a, LayoutDefinition $b) {
+    uasort($definitions, function (LayoutDefinition $a, LayoutDefinition $b) {
       if ($a->getCategory() != $b->getCategory()) {
         return strnatcasecmp($a->getCategory(), $b->getCategory());
       }
diff --git a/core/lib/Drupal/Core/ParamConverter/EntityRevisionParamConverter.php b/core/lib/Drupal/Core/ParamConverter/EntityRevisionParamConverter.php
index f7aeaf36619537f3d0c52c2ff5cdb554c068886c..afc33b6bbf7cb090348b786072d608e66733668b 100644
--- a/core/lib/Drupal/Core/ParamConverter/EntityRevisionParamConverter.php
+++ b/core/lib/Drupal/Core/ParamConverter/EntityRevisionParamConverter.php
@@ -61,7 +61,9 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Ent
    */
   public function convert($value, $definition, $name, array $defaults) {
     $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults);
-    $entity = $this->entityTypeManager->getStorage($entity_type_id)->loadRevision($value);
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage($entity_type_id);
+    $entity = $storage->loadRevision($value);
 
     // If the entity type is translatable, ensure we return the proper
     // translation object for the current context.
diff --git a/core/lib/Drupal/Core/Render/ElementInfoManager.php b/core/lib/Drupal/Core/Render/ElementInfoManager.php
index 126eb50d8d2dc8e605d62d0bc35f7c948e67091d..f87f30719e006532ee1c44697e430bf4ec40766c 100644
--- a/core/lib/Drupal/Core/Render/ElementInfoManager.php
+++ b/core/lib/Drupal/Core/Render/ElementInfoManager.php
@@ -73,7 +73,7 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
     $this->setCacheBackend($cache_backend, 'element_info');
     $this->themeManager = $theme_manager;
     if ($theme_handler instanceof CacheTagsInvalidatorInterface) {
-      @trigger_error('Calling ' . __METHOD__ . '() with the $cache_tag_invalidator argument is deprecated and replaced with $theme_handler in drupal:10.2.0 and will be removed in drupal:11.0.0.', E_USER_DEPRECATED);
+      @trigger_error('Calling ' . __METHOD__ . '() with the $cache_tag_invalidator argument is deprecated in drupal:10.2.0 and will be removed in drupal:11.0.0. Pass $theme_handler instead. See https://www.drupal.org/node/3355227', E_USER_DEPRECATED);
       $theme_handler = \Drupal::service('theme_handler');
     }
     $this->themeHandler = $theme_handler;
diff --git a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
index de2ab4ef4b7d074f9d6c938886c3d57d1da56f03..fd2d1ff0429eed7779ad6632caee2f8c7d4cb4a7 100644
--- a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
+++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
@@ -109,6 +109,7 @@ public function __construct(AssetResolverInterface $asset_resolver, ConfigFactor
     $this->renderer = $renderer;
     $this->moduleHandler = $module_handler;
     if (!isset($languageManager)) {
+      // phpcs:ignore Drupal.Semantics.FunctionTriggerError
       @trigger_error('Calling ' . __METHOD__ . '() without the $languageManager argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0', E_USER_DEPRECATED);
       $this->languageManager = \Drupal::languageManager();
     }
diff --git a/core/lib/Drupal/Core/Render/PlaceholderGenerator.php b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
index f8906c4493185b0fd25e668e57319407f9ceaf12..1cb379ea82e1e7f4e523fe4ebb1b6cd1447319e4 100644
--- a/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
+++ b/core/lib/Drupal/Core/Render/PlaceholderGenerator.php
@@ -116,7 +116,11 @@ public function createPlaceholder(array $element) {
     $callback = $placeholder_render_array['#lazy_builder'][0];
     $arguments = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);
     $token = Crypt::hashBase64(serialize($placeholder_render_array));
-    $placeholder_markup = '<drupal-render-placeholder callback="' . Html::escape($callback) . '" arguments="' . Html::escape($arguments) . '" token="' . Html::escape($token) . '"></drupal-render-placeholder>';
+    $placeholder_markup = '<drupal-render-placeholder callback="' . Html::escape($callback) . '"';
+    if ($arguments !== '') {
+      $placeholder_markup .= ' arguments="' . Html::escape($arguments) . '"';
+    }
+    $placeholder_markup .= ' token="' . Html::escape($token) . '"></drupal-render-placeholder>';
 
     // Build the placeholder element to return.
     $placeholder_element = [];
diff --git a/core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php b/core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php
index 826000959655a835dda58e93ead025de7dc1f54b..4848285d7e0951a99f349738677ed1845a7e2019 100644
--- a/core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php
+++ b/core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php
@@ -85,7 +85,7 @@ class PlaceholderingRenderCache extends RenderCache {
    */
   public function __construct(RequestStack $request_stack, $cache_factory, CacheContextsManager $cache_contexts_manager, PlaceholderGeneratorInterface $placeholder_generator) {
     if ($cache_factory instanceof CacheFactoryInterface) {
-      @trigger_error('Injecting ' . __CLASS__ . ' with the "cache_factory" service is deprecated in drupal:10.2.0, use "variation_cache_factory" instead.', E_USER_DEPRECATED);
+      @trigger_error('Injecting ' . __CLASS__ . ' with the "cache_factory" service is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use "variation_cache_factory" instead. See https://www.drupal.org/node/3365546', E_USER_DEPRECATED);
       $cache_factory = \Drupal::service('variation_cache_factory');
     }
     parent::__construct($request_stack, $cache_factory, $cache_contexts_manager);
diff --git a/core/lib/Drupal/Core/Render/RenderCache.php b/core/lib/Drupal/Core/Render/RenderCache.php
index 360cc7f5690e6ea6bece1c28071e23f615907f29..6cb15af8fc823919e20e4dcd3b45ef782164a420 100644
--- a/core/lib/Drupal/Core/Render/RenderCache.php
+++ b/core/lib/Drupal/Core/Render/RenderCache.php
@@ -47,7 +47,7 @@ class RenderCache implements RenderCacheInterface {
    */
   public function __construct(RequestStack $request_stack, $cache_factory, CacheContextsManager $cache_contexts_manager) {
     if ($cache_factory instanceof CacheFactoryInterface) {
-      @trigger_error('Injecting ' . __CLASS__ . ' with the "cache_factory" service is deprecated in drupal:10.2.0, use "variation_cache_factory" instead.', E_USER_DEPRECATED);
+      @trigger_error('Injecting ' . __CLASS__ . ' with the "cache_factory" service is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use "variation_cache_factory" instead. See https://www.drupal.org/node/3365546', E_USER_DEPRECATED);
       $cache_factory = \Drupal::service('variation_cache_factory');
     }
     $this->requestStack = $request_stack;
diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php
index 40bc5df88e8193d510c6f92509b949cb90cc3669..1ba64ef8a44ba067939ad58bd3035d2597e18f2a 100644
--- a/core/lib/Drupal/Core/Render/theme.api.php
+++ b/core/lib/Drupal/Core/Render/theme.api.php
@@ -907,31 +907,31 @@ function hook_js_alter(&$javascript, \Drupal\Core\Asset\AttachedAssetsInterface
 function hook_library_info_build() {
   $libraries = [];
   // Add a library whose information changes depending on certain conditions.
-  $libraries['mymodule.zombie'] = [
+  $libraries['zombie'] = [
     'dependencies' => [
       'core/once',
     ],
   ];
   if (Drupal::moduleHandler()->moduleExists('minifyzombies')) {
-    $libraries['mymodule.zombie'] += [
+    $libraries['zombie'] += [
       'js' => [
-        'mymodule.zombie.min.js' => [],
+        'zombie.min.js' => [],
       ],
       'css' => [
         'base' => [
-          'mymodule.zombie.min.css' => [],
+          'zombie.min.css' => [],
         ],
       ],
     ];
   }
   else {
-    $libraries['mymodule.zombie'] += [
+    $libraries['zombie'] += [
       'js' => [
-        'mymodule.zombie.js' => [],
+        'zombie.js' => [],
       ],
       'css' => [
         'base' => [
-          'mymodule.zombie.css' => [],
+          'zombie.css' => [],
         ],
       ],
     ];
@@ -943,7 +943,7 @@ function hook_library_info_build() {
   // the library (of course) not be loaded but no notices or errors will be
   // triggered.
   if (Drupal::moduleHandler()->moduleExists('vampirize')) {
-    $libraries['mymodule.vampire'] = [
+    $libraries['vampire'] = [
       'js' => [
         'js/vampire.js' => [],
       ],
@@ -1012,8 +1012,14 @@ function hook_js_settings_alter(array &$settings, \Drupal\Core\Asset\AttachedAss
  * and themes that may be using the library.
  *
  * @param array $libraries
- *   An associative array of libraries registered by $extension. Keyed by
- *   internal library name and passed by reference.
+ *   An associative array of libraries, passed by reference. The array key
+ *   for any particular library will be the name registered in *.libraries.yml.
+ *   In the example below, the array key would be $libraries['foo'].
+ *   @code
+ *   foo:
+ *     js:
+ *       .......
+ *   @endcode
  * @param string $extension
  *   Can either be 'core' or the machine name of the extension that registered
  *   the libraries.
diff --git a/core/lib/Drupal/Core/Session/SessionHandler.php b/core/lib/Drupal/Core/Session/SessionHandler.php
index 9283e677232a57087bcb518d0603c9474f77df63..e5c696826d9904342198060ddcf8dfec77086fa7 100644
--- a/core/lib/Drupal/Core/Session/SessionHandler.php
+++ b/core/lib/Drupal/Core/Session/SessionHandler.php
@@ -46,7 +46,7 @@ public function __construct(RequestStack $request_stack, Connection $connection)
    * {@inheritdoc}
    */
   #[\ReturnTypeWillChange]
-  public function open($save_path, $name) {
+  public function open(string $save_path, string $name) {
     return TRUE;
   }
 
@@ -54,7 +54,7 @@ public function open($save_path, $name) {
    * {@inheritdoc}
    */
   #[\ReturnTypeWillChange]
-  public function read(#[\SensitiveParameter] $sid) {
+  public function read(#[\SensitiveParameter] string $sid) {
     $data = '';
     if (!empty($sid)) {
       // Read the session data from the database.
@@ -69,7 +69,7 @@ public function read(#[\SensitiveParameter] $sid) {
    * {@inheritdoc}
    */
   #[\ReturnTypeWillChange]
-  public function write(#[\SensitiveParameter] $sid, $value) {
+  public function write(#[\SensitiveParameter] string $sid, $value) {
     $request = $this->requestStack->getCurrentRequest();
     $fields = [
       'uid' => $request->getSession()->get('uid', 0),
@@ -96,7 +96,7 @@ public function close() {
    * {@inheritdoc}
    */
   #[\ReturnTypeWillChange]
-  public function destroy(#[\SensitiveParameter] $sid) {
+  public function destroy(#[\SensitiveParameter] string $sid) {
     // Delete session data.
     $this->connection->delete('sessions')
       ->condition('sid', Crypt::hashBase64($sid))
@@ -109,16 +109,15 @@ public function destroy(#[\SensitiveParameter] $sid) {
    * {@inheritdoc}
    */
   #[\ReturnTypeWillChange]
-  public function gc($lifetime) {
+  public function gc(int $lifetime) {
     // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
     // value. For example, if you want user sessions to stay in your database
     // for three weeks before deleting them, you need to set gc_maxlifetime
     // to '1814400'. At that value, only after a user doesn't log in after
     // three weeks (1814400 seconds) will their session be removed.
-    $this->connection->delete('sessions')
+    return $this->connection->delete('sessions')
       ->condition('timestamp', REQUEST_TIME - $lifetime, '<')
       ->execute();
-    return TRUE;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Site/Settings.php b/core/lib/Drupal/Core/Site/Settings.php
index d9b0f9216dbf2ffa5cb0c87e21f95da3e6c8c622..97bb1cd6d4b22a586e6e1bfb0f398723ef55d857 100644
--- a/core/lib/Drupal/Core/Site/Settings.php
+++ b/core/lib/Drupal/Core/Site/Settings.php
@@ -105,6 +105,7 @@ public static function get($name, $default = NULL) {
     // If the caller is asking for the value of a deprecated setting, trigger a
     // deprecation message about it.
     if (isset(self::$deprecatedSettings[$name])) {
+      // phpcs:ignore Drupal.Semantics.FunctionTriggerError
       @trigger_error(self::$deprecatedSettings[$name]['message'], E_USER_DEPRECATED);
     }
     return self::$instance->storage[$name] ?? $default;
diff --git a/core/lib/Drupal/Core/State/State.php b/core/lib/Drupal/Core/State/State.php
index e1e62d3ed7f89c0827a541d9336c18dc004513b8..19c520d61fc5a5e62ced15a778c5659994b74781 100644
--- a/core/lib/Drupal/Core/State/State.php
+++ b/core/lib/Drupal/Core/State/State.php
@@ -57,6 +57,7 @@ public function get($key, $default = NULL) {
     // If the caller is asking for the value of a deprecated state, trigger a
     // deprecation message about it.
     if (isset(self::$deprecatedState[$key])) {
+      // phpcs:ignore Drupal.Semantics.FunctionTriggerError
       @trigger_error(self::$deprecatedState[$key]['message'], E_USER_DEPRECATED);
       $key = self::$deprecatedState[$key]['replacement'];
     }
@@ -104,6 +105,7 @@ public function getMultiple(array $keys) {
    */
   public function set($key, $value) {
     if (isset(self::$deprecatedState[$key])) {
+      // phpcs:ignore Drupal.Semantics.FunctionTriggerError
       @trigger_error(self::$deprecatedState[$key]['message'], E_USER_DEPRECATED);
       $key = self::$deprecatedState[$key]['replacement'];
     }
diff --git a/core/lib/Drupal/Core/StringTranslation/ByteSizeMarkup.php b/core/lib/Drupal/Core/StringTranslation/ByteSizeMarkup.php
new file mode 100644
index 0000000000000000000000000000000000000000..766904e4daf192a1f02885bc76ff3c7079f11cdb
--- /dev/null
+++ b/core/lib/Drupal/Core/StringTranslation/ByteSizeMarkup.php
@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\StringTranslation;
+
+use Drupal\Component\Utility\Bytes;
+
+/**
+ * A class to generate translatable markup for the given byte count.
+ */
+final class ByteSizeMarkup {
+
+  /**
+   * This class should not be instantiated.
+   */
+  private function __construct() {}
+
+  /**
+   * Gets the TranslatableMarkup object for the provided size.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
+   *   The translatable markup.
+   *
+   * @throws \LogicException
+   *   Thrown when an invalid unit size is used.
+   */
+  public static function create(float|int $size, string $langcode = NULL, TranslationInterface $stringTranslation = NULL): TranslatableMarkup {
+    $options = ['langcode' => $langcode];
+    $absolute_size = abs($size);
+    if ($absolute_size < Bytes::KILOBYTE) {
+      return new PluralTranslatableMarkup($size, '1 byte', '@count bytes', [], $options, $stringTranslation);
+    }
+    // Create a multiplier to preserve the sign of $size.
+    $sign = $absolute_size / $size;
+    foreach (['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] as $unit) {
+      $absolute_size /= Bytes::KILOBYTE;
+      $rounded_size = round($absolute_size, 2);
+      if ($rounded_size < Bytes::KILOBYTE) {
+        break;
+      }
+    }
+
+    $args = ['@size' => $rounded_size * $sign];
+    // At this point $markup must be set.
+    return match ($unit) {
+      'KB' => new TranslatableMarkup('@size KB', $args, $options, $stringTranslation),
+      'MB' => new TranslatableMarkup('@size MB', $args, $options, $stringTranslation),
+      'GB' => new TranslatableMarkup('@size GB', $args, $options, $stringTranslation),
+      'TB' => new TranslatableMarkup('@size TB', $args, $options, $stringTranslation),
+      'PB' => new TranslatableMarkup('@size PB', $args, $options, $stringTranslation),
+      'EB' => new TranslatableMarkup('@size EB', $args, $options, $stringTranslation),
+      'ZB' => new TranslatableMarkup('@size ZB', $args, $options, $stringTranslation),
+      'YB' => new TranslatableMarkup('@size YB', $args, $options, $stringTranslation),
+      default => throw new \LogicException("Unexpected unit value"),
+    };
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php b/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
index 9b8d675b28f2d134fdaae940b2d3f8694a6dfef1..4fbfff01cdd0f552a7184e550cc56e0d7df8e468 100644
--- a/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
+++ b/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
@@ -48,6 +48,7 @@ public function __invoke() {
                       // Fire the same deprecation message to allow it to be
                       // collected by
                       // \Symfony\Bridge\PhpUnit\DeprecationErrorHandler::collectDeprecations().
+                      // phpcs:ignore Drupal.Semantics.FunctionTriggerError
                       @trigger_error((string) $parameters[0], E_USER_DEPRECATED);
                     }
                     else {
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index f0e6118a398cf63a2d7c3a0e0a886fc5f5e4414d..07e83ccfa1fbcb98b07dbc698b03518ec385b825 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -329,8 +329,8 @@ public function getBaseHook($hook) {
    * See the @link themeable Default theme implementations topic @endlink for
    * details.
    *
-   * @return \Drupal\Core\Utility\ThemeRegistry
-   *   The build theme registry.
+   * @return array
+   *   The built theme registry.
    *
    * @see hook_theme_registry_alter()
    */
diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php
index 7cc3f3f66e107c828597fc2170b878fa3d0abc74..d43ad7ea55c85fa76e03363ea79f44241fcabaf9 100644
--- a/core/lib/Drupal/Core/Updater/Module.php
+++ b/core/lib/Drupal/Core/Updater/Module.php
@@ -80,8 +80,14 @@ public static function canUpdate($project_name) {
    * Returns available database schema updates once a new version is installed.
    *
    * @return array
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   * \Drupal\Core\Update\UpdateHookRegistry::getAvailableUpdates() instead.
+   *
+   * @see https://www.drupal.org/node/3359445
    */
   public function getSchemaUpdates() {
+    @trigger_error(__METHOD__ . "() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\Update\UpdateHookRegistry::getAvailableUpdates() instead. See https://www.drupal.org/node/3359445", E_USER_DEPRECATED);
     require_once DRUPAL_ROOT . '/core/includes/install.inc';
     require_once DRUPAL_ROOT . '/core/includes/update.inc';
 
diff --git a/core/lib/Drupal/Core/Utility/Token.php b/core/lib/Drupal/Core/Utility/Token.php
index e9f1732051f3a93d8662aab84037f695df41decd..d6f6c0190f8baae0611550eef98c19cf98ddeb2a 100644
--- a/core/lib/Drupal/Core/Utility/Token.php
+++ b/core/lib/Drupal/Core/Utility/Token.php
@@ -290,7 +290,7 @@ protected function doReplace(bool $markup, string $text, array $data, array $opt
    */
   public function scan($text) {
     if (!is_string($text)) {
-      @trigger_error('Calling ' . __METHOD__ . '() with a $text parameter of type other than string is deprecated in drupal:10.1.0, a typehint will be added in drupal:11.0.0. See https://www.drupal.org/node/3334317', E_USER_DEPRECATED);
+      @trigger_error('Calling ' . __METHOD__ . '() with a $text parameter of type other than string is deprecated in drupal:10.1.0 and will cause an error in drupal:11.0.0. See https://www.drupal.org/node/3334317', E_USER_DEPRECATED);
       $text = (string) $text;
     }
 
diff --git a/core/misc/cspell/dictionary.txt b/core/misc/cspell/dictionary.txt
index a4cd7d4c83f08dec90ba76227815186e8e0b7f03..a8969359b43fd42d859f6fe02ca19d18210873d4 100644
--- a/core/misc/cspell/dictionary.txt
+++ b/core/misc/cspell/dictionary.txt
@@ -18,7 +18,6 @@ ajaxing
 akiko
 allwords
 alphadecimal
-alterjs
 alternatif
 amet
 amphibius
@@ -122,6 +121,7 @@ blocknodebundle
 blockpromoted
 blockquotes
 blockrecipe
+blockrelated
 blocktest
 bloggy
 blub
@@ -131,8 +131,6 @@ bodyvalue
 boing
 bomofo
 bonacieux
-boskoop
-boskop
 bovigo
 breezer
 brion
@@ -306,6 +304,7 @@ desaturate
 desaturated
 desaturates
 desaturating
+descoping
 descripcion
 destructable
 deutsch
@@ -322,9 +321,11 @@ distro
 ditka
 divs
 dnumber
+dockerhub
 docroot
 docroots
 dolore
+dotenv
 downcasting
 doxygen
 dragtable
@@ -339,7 +340,6 @@ drivertext
 dropbutton
 dropbuttons
 drudbal
-drup
 drupalci
 drupaldatetime
 drupaldevdays
@@ -364,7 +364,6 @@ drush
 drépal
 détruire
 editables
-editunblock
 eerste
 egulias
 eins
@@ -433,7 +432,6 @@ fieldsets
 filelist
 filemime
 filesort
-filestorage
 filesystems
 filetransfer
 filevalidationerror
@@ -450,7 +448,6 @@ fooalert
 foobarbaz
 foobargorilla
 foofoo
-foos
 formattable
 formvalidation
 fouc
@@ -540,6 +537,7 @@ insidekeyword
 instaclick
 instantiator
 interactable
+interruptible
 introspectable
 invalidators
 invalididentifier
@@ -667,6 +665,7 @@ metatag
 metatags
 meφω
 mglaman
+micheh
 mikey
 milli
 mimetypes
@@ -737,7 +736,6 @@ negotiatiors
 newcol
 newfieldinitial
 newnode
-newstr
 newterm
 newwin
 nids
@@ -795,6 +793,7 @@ optin
 optionchecker
 orgchart
 ossp
+otel
 otlp
 otsikko
 outdent
@@ -885,8 +884,6 @@ presetname
 pretransaction
 preuninstall
 processlist
-projecta
-projectb
 proname
 prophesize
 prophesized
@@ -967,7 +964,6 @@ revlog
 revpub
 ribisi
 ritchie
-rolename
 roly
 routable
 routeable
@@ -1027,7 +1023,6 @@ somemodule
 someschema
 somethinggeneric
 sortablejs
-sourcedir
 sourceediting
 spacebar
 spagna
@@ -1130,7 +1125,6 @@ tagstack
 tagwords
 takeshita
 tappable
-targetdir
 tarz
 taskless
 tatou
diff --git a/core/misc/form.js b/core/misc/form.js
index 0e51d833f93d9378700c14fee835358982500368..3256a17fd82c8f2c9345e089b0631f3eff8012ba 100644
--- a/core/misc/form.js
+++ b/core/misc/form.js
@@ -31,7 +31,14 @@
    */
   $.fn.drupalGetSummary = function () {
     const callback = this.data('summaryCallback');
-    return this[0] && callback ? callback(this[0]).trim() : '';
+
+    if (!this[0] || !callback) {
+      return '';
+    }
+
+    const result = callback(this[0]);
+
+    return result ? result.trim() : '';
   };
 
   /**
diff --git a/core/misc/icons/55565b/boolean.svg b/core/misc/icons/55565b/boolean.svg
index f6cb6782f06a94ce9d1a7097e4f20b472bfea9fb..1667c68bdf692624c2170a2e886ff260d5f1e54b 100644
--- a/core/misc/icons/55565b/boolean.svg
+++ b/core/misc/icons/55565b/boolean.svg
@@ -1,4 +1 @@
-<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<rect x="1" y="1" width="22" height="14" rx="7" stroke="#55565B" stroke-width="2"/>
-<rect x="12" y="4" width="8" height="8" rx="4" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m11.02 6.064c-2.287.164-4.788 1.165-6.58 2.634-1.741 1.427-3.084 3.366-3.786 5.466-.852 2.547-.853 5.12-.006 7.656 1.506 4.503 5.535 7.679 10.302 8.119.884.082 13.216.082 14.1 0 5.287-.488 9.574-4.301 10.683-9.502.649-3.043.026-6.328-1.707-8.989a11.927 11.927 0 0 0 -9.157-5.386c-.977-.071-12.861-.069-13.849.002m14.422 2.542c4.167.683 7.319 3.848 7.953 7.984.165 1.079.088 2.688-.182 3.75-.944 3.727-4.045 6.501-7.923 7.088-.789.12-13.787.12-14.58.001-3.514-.53-6.376-2.828-7.627-6.126-.631-1.664-.746-3.857-.295-5.645.918-3.647 3.936-6.404 7.712-7.047.692-.118 14.227-.122 14.942-.005m-2.702 2.548c-2.256.498-3.999 2.206-4.569 4.476-.156.618-.219 2.389-.115 3.18.4 3.027 2.917 5.25 5.944 5.25a5.87 5.87 0 0 0 4.37-1.894 6.1 6.1 0 0 0 1.576-3.415c.1-.847.038-2.503-.117-3.121-.446-1.782-1.586-3.196-3.219-3.994-.879-.43-1.377-.546-2.46-.573-.72-.017-1.002.001-1.41.091" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/comment.svg b/core/misc/icons/55565b/comment.svg
index 9548d11fee43876d27c056878ab87579c7710fda..e80ec00abecd5f006d1b9a409516fe77af707068 100644
--- a/core/misc/icons/55565b/comment.svg
+++ b/core/misc/icons/55565b/comment.svg
@@ -1,3 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="22" height="21" fill="none">
-  <path d="M13 20.5L10.2 17H5a1 1 0 0 1-1-1V5.103a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1V16a1 1 0 0 1-1 1h-5.2L13 20.5zm1.839-5.5H20V6.103H6V15h5.161L13 17.298 14.839 15zM1 0h17v2H2v11H0V1a1 1 0 0 1 1-1z" fill="#55565b"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m21.0909 34-4.3273-5.4634h-8.03633c-.40988 0-.80297-.1645-1.0928-.4572-.28983-.2928-.45265-.6898-.45265-1.1038v-17.00994c0-.414.16282-.81104.45265-1.10378s.68292-.4572 1.0928-.4572h24.72723c.4099 0 .803.16446 1.0928.4572.2899.29274.4527.68978.4527 1.10378v17.00994c0 .414-.1628.811-.4527 1.1038-.2898.2927-.6829.4572-1.0928.4572h-8.0363zm2.8421-8.5854h7.9761v-13.888h-21.6364v13.888h7.9761l2.8421 3.5872zm-21.38755-23.4146h26.27275v3.12195h-24.72729v17.17075h-3.09091v-18.73172c0-.414.16282-.81104.45265-1.10378s.68292-.4572 1.0928-.4572z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/date_and_time.svg b/core/misc/icons/55565b/date_and_time.svg
index 42f5dc730c96237e82275a8b479e09d4f6d23237..00af00670e511c2d5701611619b858a461595346 100644
--- a/core/misc/icons/55565b/date_and_time.svg
+++ b/core/misc/icons/55565b/date_and_time.svg
@@ -1,5 +1 @@
-<svg width="23" height="21" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path d="M19 14.857h-2V12h-1v4h3v-1.143Z" fill="#55565B"/>
-  <path d="M17.125 1.75h-4.004V0h-1.75v1.75H6.123V0h-1.75v1.75H.876A.875.875 0 0 0 0 2.623v14.501c0 .483.392.875.875.875h11.248a5.603 5.603 0 0 1-.954-2H2V6.123h14v2.911a5.69 5.69 0 0 1 2 .135V2.624a.875.875 0 0 0-.875-.875Z" fill="#55565B"/>
-  <path fill-rule="evenodd" clip-rule="evenodd" d="M16.626 18.503a3.877 3.877 0 1 0 0-7.753 3.877 3.877 0 0 0 0 7.753Zm0 1.75a5.627 5.627 0 1 0 0-11.253 5.627 5.627 0 0 0 0 11.253Z" fill="#55565B"/>
-</svg>
+<svg height="33" viewBox="0 0 36 33" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m7.08 1.44v1.44h-5.976l-.318.158a1.326 1.326 0 0 0 -.726.941c-.048.224-.061 3.678-.048 12.311l.018 12 .131.246c.073.135.228.329.345.432.448.393-.104.373 9.978.372l9.14-.002.288.346c.479.574 1.348 1.362 1.936 1.755a9.006 9.006 0 0 0 8.182.98c4.629-1.704 7.072-6.881 5.452-11.555-.939-2.711-3.044-4.81-5.725-5.709l-.627-.211-.03-5.537-.03-5.537-.133-.249c-.162-.303-.513-.603-.797-.682-.125-.035-1.57-.058-3.555-.059h-3.345v-2.88h-2.82v2.88h-8.52v-2.88h-2.82zm18.84 10.912v2.391l-.342.041c-.542.063-1.317.269-1.969.521-2.825 1.095-4.943 3.609-5.613 6.664-.235 1.07-.219 2.683.039 3.936l.04.195h-14.835v-16.14h22.68zm1.185 2.332a2.601 2.601 0 0 1 -.45 0c-.124-.013-.022-.024.225-.024s.349.011.225.024m1.332 3.012c.586.148 1.445.539 1.976.899a6.322 6.322 0 0 1 2.746 5.525c-.079 1.624-.71 3.058-1.845 4.194a5.756 5.756 0 0 1 -1.756 1.24c-.918.435-1.576.581-2.618.583-.585.001-1.008-.03-1.292-.094-2.621-.594-4.532-2.609-4.95-5.219-.107-.664-.045-1.976.121-2.594.636-2.361 2.568-4.177 4.912-4.62.665-.125 2.042-.081 2.706.086m-2.563 5.119.016 3.255 2.415.016 2.415.015v-1.859l-1.605-.016-1.605-.016-.016-2.325-.015-2.325h-1.62z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/daterange.svg b/core/misc/icons/55565b/daterange.svg
index 53481d2bfadf28d37e47415192fdc5d4292a0ea8..8b852f0d95cea093ac128c9603ba26453151c8a3 100644
--- a/core/misc/icons/55565b/daterange.svg
+++ b/core/misc/icons/55565b/daterange.svg
@@ -1,3 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none">
-  <path d="M15 2h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h4V0h2v2h6V0h2v2ZM2 8v10h16V8H2Zm2 2h2v2H4v-2Zm5 0h2v2H9v-2Zm5 0h2v2h-2v-2Z" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m27 3.6h7.2c.9941 0 1.8.8059 1.8 1.8v28.8c0 .9941-.8059 1.8-1.8 1.8h-32.4c-.994104 0-1.8-.8059-1.8-1.8v-28.8c0-.9941.805896-1.8 1.8-1.8h7.2v-3.6h3.6v3.6h10.8v-3.6h3.6zm-23.4 10.8v18h28.8v-18zm3.6 3.6h3.6v3.6h-3.6zm9 0h3.6v3.6h-3.6zm9 0h3.6v3.6h-3.6z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/email.svg b/core/misc/icons/55565b/email.svg
index 143b2b51d92e9d326b7b9e489083c7685458dc6f..331f7f99cc0b25a0ec9cc3b0b1cc76291ba86940 100644
--- a/core/misc/icons/55565b/email.svg
+++ b/core/misc/icons/55565b/email.svg
@@ -1,3 +1 @@
-<svg width="17" height="17" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path d="M8.954.748C6.498.738 3.977 1.815 2.538 3.84 1.041 5.917.663 8.658 1.158 11.135c.35 1.653 1.316 3.216 2.796 4.08 1.688 1.02 3.747 1.173 5.671.953a9.483 9.483 0 0 0 2.8-.78v-1.83c-1.985.823-4.254 1.224-6.364.633-1.482-.418-2.687-1.696-3.013-3.2-.39-1.533-.295-3.18.198-4.675.528-1.494 1.674-2.787 3.178-3.33a7.097 7.097 0 0 1 4.756-.134c1.364.5 2.477 1.679 2.817 3.091.354 1.293.314 2.721-.186 3.965-.22.525-.72 1.06-1.34.94a.783.783 0 0 1-.647-.692c-.183-.867.042-1.753.028-2.63.047-.886.094-1.772.143-2.659-1.4-.419-2.911-.688-4.355-.35-1.462.4-2.583 1.716-2.827 3.206-.214 1.11-.121 2.308.392 3.317.474.865 1.393 1.48 2.389 1.519 1.098.103 2.269-.356 2.893-1.284.407.928 1.495 1.456 2.483 1.268 1.18-.162 2.131-1.085 2.547-2.167.687-1.605.656-3.453.18-5.112-.568-1.831-1.982-3.398-3.806-4.035A7.905 7.905 0 0 0 8.954.748ZM8.838 6.07c.389-.001.778.03 1.155.112-.11 1.265.025 2.59-.461 3.788-.233.506-.741.899-1.308.891-.594.059-1.253-.29-1.392-.902-.265-.812-.187-1.711.056-2.517.271-.74.963-1.355 1.779-1.363a2.63 2.63 0 0 1 .171-.01Z" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m18.905 3.0034c-4.8818-.0181-9.89368 2.11996-12.75421 6.14588-2.97685 4.12852-3.72724 9.57882-2.74182 14.50242.69215 3.2862 2.61415 6.3919 5.55652 8.109 3.35621 2.0297 7.44861 2.3335 11.27361 1.8957 1.9198-.2327 3.8097-.7456 5.5656-1.549 0-1.2133 0-2.4267 0-3.64-3.9461 1.6362-8.4574 2.4333-12.6514 1.2587-2.9465-.8305-5.34152-3.3703-5.98961-6.3624-.77366-3.0458-.58571-6.3211.39477-9.2927 1.05002-2.9697 3.32674-5.53882 6.31624-6.61934 2.9829-1.16767 6.4097-1.27462 9.4541-.26651 2.7133.99524 4.9245 3.33755 5.6015 6.14525.7038 2.5698.6228 5.4088-.3714 7.8826-.4383 1.0424-1.4289 2.1055-2.6643 1.867-.6836-.1102-1.2174-.6898-1.2841-1.374-.3646-1.7236.0832-3.4856.0543-5.2278.0939-1.7622.1876-3.5244.2846-5.2865-2.7816-.8329-5.7863-1.36856-8.6563-.6962-2.9057.7966-5.1346 3.4114-5.6209 6.3736-.4246 2.2055-.2402 4.5877.7799 6.5936.9431 1.7193 2.7689 2.9433 4.7485 3.0192 2.1842.205 4.5109-.7068 5.752-2.5513.808 1.8442 2.9703 2.8932 4.9355 2.5197 2.3445-.3217 4.2363-2.1564 5.0624-4.3086 1.3658-3.1906 1.3042-6.8642.3573-10.1616-1.129-3.63941-3.9388-6.75356-7.5656-8.02092-1.8577-.69892-3.8521-.9948-5.8372-.95578zm-.2305 10.5789c.7719-.0025 1.547.0602 2.296.2236-.2194 2.5144.0496 5.147-.9169 7.5287-.4626 1.006-1.4737 1.788-2.6009 1.773-1.18.1157-2.4907-.5777-2.7663-1.7944-.5272-1.6144-.3716-3.4013.1106-5.0038.5405-1.4722 1.9158-2.6924 3.5363-2.7087.1134-.0098.2273-.016.3412-.0184z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/file_upload.svg b/core/misc/icons/55565b/file_upload.svg
index 39bbdeccbda63ee6f03c1aada453946d0dc99609..4f3ed73ea194fcfe191c6bbafd3ddc964ccb5aad 100644
--- a/core/misc/icons/55565b/file_upload.svg
+++ b/core/misc/icons/55565b/file_upload.svg
@@ -1,4 +1 @@
-<svg width="18" height="20" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path d="M12 2H2v16h14V6h-4V2ZM0 .992C0 .444.447 0 .999 0H13l5 5v13.992A1 1 0 0 1 17.007 20H.993A1 1 0 0 1 0 19.008V.992Z" fill="#55565B"/>
-  <path d="M10.25 13v3h-2.5v-3H5l4-5 4 5h-2.75Z" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m3.87 1.163c-.361.17-.581.394-.745.757-.122.269-.122.303-.123 16.08l-.001 15.81.141.303c.166.355.54.699.87.797.173.052 3.612.07 13.532.07 14.778 0 13.513.037 13.978-.408.128-.122.282-.33.344-.462.107-.232.111-.689.113-12.93l.001-12.69-3.735-3.735-3.735-3.735-10.17.001h-10.17zm19.11 5.857v3h6v21.96h-22.98v-27.96h16.98zm-9.215 11.981-3.703 3.949 1.959.016 1.959.016v5.998h7.02v-5.998l1.969-.016 1.968-.016-3.684-3.93c-2.027-2.162-3.707-3.938-3.734-3.949-.028-.01-1.717 1.759-3.754 3.93" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/formatted_text.svg b/core/misc/icons/55565b/formatted_text.svg
index 916f8c7dc328e22fc0d06024afffea047e3f7d5d..6492eb303f33e0dd31913d559a60903a248b9f3e 100644
--- a/core/misc/icons/55565b/formatted_text.svg
+++ b/core/misc/icons/55565b/formatted_text.svg
@@ -1,4 +1 @@
-<svg width="24" height="14" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path fill-rule="evenodd" clip-rule="evenodd" d="M24 2H14V0h10v2ZM24 8H11V6h13v2ZM24 14H9v-2h15v2Z" fill="#55565B"/>
-  <path d="M0 13c.612 0 1.272.123 1.5 0 .365-.198.399-.352.581-1.034L5.003 1C4.007.993 3.21.989 2.5 1.5c-.71.504-1.054 1.487-1.523 2.475H0L.977 0H12v4l-1.25-.025c-.033-1.362-.398-2.292-1.095-2.79C9.35.968 8.5 1 8 1L5.17 11.435l-.167.726a1.738 1.738 0 0 0-.049.419c0 .36 0 .325.215.42.215.089 1.147-.048 1.831 0v1H0v-1Z" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m21 9.48v1.5h15v-3h-15zm-20.236 1.425c-.388 1.576-.705 2.913-.704 2.972 0 .097.062.105.698.09l.699-.017.459-.99c.567-1.222.894-1.777 1.322-2.241.813-.88 1.883-1.239 3.696-1.239h.579l-.04.165c-.449 1.841-4.58 17.152-4.673 17.32a1.696 1.696 0 0 1 -.362.42l-.231.185-1.104-.017-1.103-.016v1.443h10.5v-.716c0-.696-.004-.716-.135-.738-.074-.012-.676-.01-1.337.005-1.038.022-1.228.012-1.395-.074-.191-.099-.193-.105-.193-.542 0-.641.125-1.135 2.45-9.695l2.095-7.71.892.006c1.115.008 1.444.091 1.871.474.782.703 1.239 1.865 1.362 3.465l.041.525h1.849v-5.94h-16.532zm15.736 7.605v1.47h19.5v-2.94h-19.5zm-3 9v1.47h22.5v-2.94h-22.5z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/link.svg b/core/misc/icons/55565b/link.svg
index 7935ca9c6c0ca9ec7b252b926a9e0a25e29a34c2..7d8701b1e4de6189aecb49b01bbcffcab02eb5c8 100644
--- a/core/misc/icons/55565b/link.svg
+++ b/core/misc/icons/55565b/link.svg
@@ -1,4 +1 @@
-<svg width="21" height="20" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path d="M8.197 10.906a4.529 4.529 0 0 0 6.832.49l2.718-2.719a4.53 4.53 0 0 0-6.406-6.405L9.783 3.82" stroke="#55565B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
-  <path d="M11.821 9.094a4.53 4.53 0 0 0-6.831-.49l-2.718 2.719a4.53 4.53 0 0 0 6.405 6.405l1.55-1.549" stroke="#55565B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
-</svg>
+<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg"><path d="m25.84 2.37c-1.972.311-3.605 1.099-5.162 2.493-1.157 1.035-3.04 2.995-3.221 3.353a1.34 1.34 0 0 0 -.018 1.252c.193.415.819.997 1.219 1.133.798.272 1.01.143 2.997-1.818.891-.88 1.734-1.688 1.873-1.796.678-.523 1.686-.971 2.555-1.135.663-.125 2.055-.089 2.622.068 1.75.484 3.119 1.611 3.911 3.219.493.999.595 1.459.593 2.673-.002 1.102-.093 1.593-.444 2.395-.437.998-.716 1.326-3.528 4.137-1.468 1.467-2.844 2.795-3.058 2.951-1.167.846-2.819 1.293-4.162 1.126a6.606 6.606 0 0 1 -2.194-.674c-.836-.445-1.081-.654-2.231-1.909-.385-.42-.706-.585-1.139-.584-.431.001-.898.215-1.313.604-.579.541-.721 1.135-.423 1.773.157.339.916 1.282 1.378 1.712a9.753 9.753 0 0 0 3.617 2.108c.98.314 1.471.395 2.613.432.91.03 1.195.015 1.842-.096a9.098 9.098 0 0 0 2.767-.918c1.263-.639 1.688-1.007 4.862-4.201 2.382-2.397 2.954-3.006 3.28-3.496 1.732-2.599 2.122-5.727 1.075-8.622-1.126-3.113-3.765-5.388-7.049-6.079-.818-.172-2.484-.224-3.262-.101m-10.64 10.783c-1.249.2-2.102.463-3.071.946-1.308.651-1.648.941-4.727 4.012-1.669 1.666-2.97 3.018-3.178 3.302-.899 1.23-1.444 2.426-1.758 3.857-.168.763-.233 2.364-.128 3.113.583 4.136 3.564 7.335 7.605 8.161 2.581.528 5.344-.096 7.537-1.7.261-.191 1.234-1.1 2.162-2.02 1.865-1.851 2.043-2.083 2.047-2.677.003-.427-.133-.719-.538-1.163-.35-.383-.785-.62-1.212-.661-.581-.056-.836.131-2.744 2.013-1.74 1.715-2.089 2.001-2.908 2.379-.895.414-1.338.502-2.507.499-.947-.002-1.096-.018-1.592-.171-.737-.227-1.185-.431-1.713-.783-1.1-.731-1.953-1.812-2.37-3.006-.489-1.401-.452-3.071.097-4.364.449-1.056.614-1.252 3.451-4.107 1.466-1.475 2.829-2.809 3.03-2.964 1.652-1.284 3.976-1.616 5.891-.842 1.036.419 1.703.931 2.81 2.16.537.595 1.024.749 1.675.527.388-.132.966-.601 1.17-.951.338-.576.258-1.146-.258-1.835-1.526-2.036-3.759-3.341-6.333-3.703-.425-.06-2.108-.075-2.438-.022" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/media.svg b/core/misc/icons/55565b/media.svg
index 8e675d2a41bcdbbdbe2eea622712bc8173575211..cec884d8381faa2843c37517e7de36a9cee7953a 100644
--- a/core/misc/icons/55565b/media.svg
+++ b/core/misc/icons/55565b/media.svg
@@ -1,11 +1 @@
-<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <g clip-path="url(#a)" fill-rule="evenodd" clip-rule="evenodd" fill="#55565B">
-    <path d="m19.187 4.864-8.936 2v.16h-.001v8.485a2.401 2.401 0 0 0-1.91-.195c-1.22.376-1.93 1.583-1.587 2.696.343 1.113 1.61 1.71 2.83 1.334 1.084-.334 1.765-1.324 1.664-2.32h.003V9.801l6.93-1.551v6.136a2.401 2.401 0 0 0-1.909-.196c-1.22.376-1.93 1.583-1.587 2.696.343 1.113 1.61 1.71 2.83 1.335 1.083-.334 1.765-1.324 1.663-2.32h.003V8.026l.007-.002v-3.16Z"/>
-    <path d="M13.504.744H.387V12.16h13.117V.744Zm-1.166 1.014H1.553v9.387h.104l2.35-2.281 2.056 1.997L9.25 5.913l2.131 3.309.395-.554.562.788V1.758ZM6.22 4.508a1.943 1.943 0 1 1-3.886 0 1.943 1.943 0 0 1 3.886 0Z"/>
-  </g>
-  <defs>
-    <clipPath id="a">
-      <path fill="#fff" d="M0 0h20v20H0z"/>
-    </clipPath>
-  </defs>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m1.02 11.52v10.5h17.46v5.203l-.495-.198a4.21 4.21 0 0 0 -2.493-.23c-.951.194-1.625.559-2.324 1.258-.989.988-1.371 2.274-1.049 3.531.171.672.441 1.137.96 1.658.74.741 1.733 1.138 2.846 1.138 2.017-.002 3.781-1.311 4.26-3.162.082-.317.095-.954.095-4.781v-4.416l2.355-.015 2.355-.016.016-3.06.015-3.06 3.855-.858c2.12-.472 3.874-.857 3.899-.855.05.003.063 11.043.013 11.043-.018 0-.161-.066-.319-.146-.669-.339-1.648-.461-2.5-.313a4.415 4.415 0 0 0 -2.489 1.3c-1.49 1.513-1.514 3.723-.058 5.169.774.768 1.682 1.127 2.853 1.129 1.701.003 3.245-.922 3.957-2.369.4-.812.375-.108.409-11.535.029-9.661.024-10.395-.069-10.395-.055 0-2.21.472-4.79 1.05-2.579.578-4.706 1.05-4.726 1.05s-.036-2.052-.036-4.56v-4.56h-24v10.5m21.96-4.715v3.805l-.555.125-2.265.511-1.71.385-.15-.133-.707-.634c-.306-.275-.565-.49-.575-.477s-1.309 2.03-2.887 4.483l-2.87 4.46-.905-.89-1.85-1.818-.944-.929-.256.243-4.081 3.857-.225.211v-17.004h19.98zm-15.69-2.415c-.545.102-1.263.499-1.703.94-.696.699-1.027 1.507-1.027 2.51 0 1.932 1.531 3.45 3.48 3.45a3.453 3.453 0 0 0 3.479-3.472c.001-.606-.09-.989-.378-1.578-.682-1.399-2.248-2.151-3.851-1.85" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/number.svg b/core/misc/icons/55565b/number.svg
index 709a04a8d8e57508979252ad9466ff8d136efee0..c3f693570c1df5f24751f9a6b24d9bc93b051ebc 100644
--- a/core/misc/icons/55565b/number.svg
+++ b/core/misc/icons/55565b/number.svg
@@ -1,4 +1 @@
-<svg width="24" height="14" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path fill-rule="evenodd" clip-rule="evenodd" d="M8.654 4.697H6.848v-.292C6.848 2.303 8.6.6 10.76.6c2.16 0 3.913 1.703 3.913 3.805 0 .907-.327 1.74-.872 2.394l-.003.004-3.992 4.625h4.867v1.757H6.848v-1.06l5.527-6.404c.307-.356.493-.815.493-1.316 0-1.132-.944-2.049-2.107-2.049-1.164 0-2.107.917-2.107 2.049v.292ZM23.216.976v1.2l-2.703 3.332C22.23 5.962 23.5 7.572 23.5 9.49c0 2.27-1.78 4.11-3.975 4.11-1.863 0-3.426-1.325-3.857-3.113l-.068-.284 1.652-.428.07.285c.245 1.022 1.14 1.778 2.203 1.778 1.254 0 2.271-1.051 2.271-2.348S20.78 7.14 19.525 7.14a2.2 2.2 0 0 0-1.008.244l-.37.204-.626-1.135 3.016-3.717h-4.419V.976h7.098Z" fill="#55565B"/>
-  <path d="M4.891.976v12.209H2.935V2.88L0 3.566V1.802L3.544.976h1.347Z" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m15.497 8.045c-1.353.129-2.556.713-3.559 1.727-1.08 1.092-1.675 2.516-1.677 4.013l-.001.555h2.683l.032-.585c.064-1.137.567-1.998 1.515-2.591.955-.598 2.344-.579 3.304.046 1.046.68 1.594 1.871 1.419 3.085-.134.93-.07.844-4.719 6.377l-4.231 5.038-.002.825-.001.825h11.7v-2.699l-3.627-.016-3.628-.015 2.974-3.54c1.635-1.947 3.08-3.689 3.212-3.87.322-.446.668-1.176.866-1.83.148-.487.164-.634.168-1.5.003-.82-.016-1.035-.133-1.47-.174-.647-.634-1.595-1.02-2.104-1.223-1.611-3.215-2.469-5.275-2.271m-12.872 1.184-2.625.635v1.338c0 .736.01 1.338.023 1.338.012 0 .91-.213 1.995-.473 1.085-.261 2.06-.492 2.167-.515l.195-.042v15.85h2.94v-18.78l-1.035.007-1.035.007zm21.495.701v1.35h3.3c1.815 0 3.3.013 3.3.028 0 .023-4.162 5.318-4.411 5.612-.064.075-.004.224.366.9.243.445.45.832.46.859.01.028.233-.06.496-.195 1.06-.541 1.997-.569 3.012-.087.814.387 1.449 1.12 1.781 2.06.161.457.181.589.181 1.203.001.492-.03.793-.108 1.05-.534 1.778-2.246 2.891-3.886 2.527-1.343-.299-2.334-1.279-2.686-2.655-.082-.322-.129-.41-.211-.403-.058.005-.602.14-1.209.3-.864.228-1.105.312-1.105.389 0 .214.317 1.188.538 1.654.833 1.753 2.35 2.971 4.166 3.345.74.153 1.734.13 2.465-.055a5.578 5.578 0 0 0 2.596-1.435c3.055-2.897 2.51-8.072-1.077-10.218a6 6 0 0 0 -.9-.424c-.257-.091-.467-.179-.467-.195s.905-1.175 2.01-2.574l2.009-2.544v-1.842h-10.62z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/plain_text.svg b/core/misc/icons/55565b/plain_text.svg
index 24f1f075b016bad2894739ddb3acc1eb756e44fd..8547f05fa0a00c2e04cf85586293ad6fd0591ff3 100644
--- a/core/misc/icons/55565b/plain_text.svg
+++ b/core/misc/icons/55565b/plain_text.svg
@@ -1,3 +1,3 @@
-<svg width="12" height="15" viewBox="0 0 12 15" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M0 2V0H12V2H7V15H5V2H0Z" fill="#55565B"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 36 36">
+  <path fill="#55565B" d="M7 7.60001V4H29V7.60001H19.8333V31H16.1667V7.60001H7Z"/>
 </svg>
diff --git a/core/misc/icons/55565b/reference.svg b/core/misc/icons/55565b/reference.svg
index 92f5b08b7265cce3e88a787d1b0e07915bf3cc26..471932ae95a30b44ec84de470ec85a5145d7bae4 100644
--- a/core/misc/icons/55565b/reference.svg
+++ b/core/misc/icons/55565b/reference.svg
@@ -1,3 +1 @@
-<svg width="18" height="18" fill="none" xmlns="http://www.w3.org/2000/svg">
-  <path d="M12 0a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-2v2h4a1 1 0 0 1 1 1v3h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2v-2H5v2h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2V9a1 1 0 0 1 1-1h4V6H6a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h6ZM6 14H2v2h4v-2Zm10 0h-4v2h4v-2ZM11 2H7v2h4V2Z" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m10.98 9v4.98h5.52v3.06h-9v6h-5.52v9.96h14.04v-9.96h-5.52v-3.06h15v3.06h-5.52v9.96h14.04v-9.96h-5.52v-6h-9v-3.06h5.52v-9.96h-14.04zm11.026.015-.016 1.995h-7.98l-.016-1.995-.016-1.995h8.044zm-8.986 18.975v2.01h-8.04v-4.02h8.04zm18 0v2.01h-8.04v-4.02h8.04z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/selection_list.svg b/core/misc/icons/55565b/selection_list.svg
index 6949da6a0242b78fbf679ffcaa4e5a4b545c3e7e..c4e86388e4e2cff31bad77c1c6aff5e34dd0260d 100644
--- a/core/misc/icons/55565b/selection_list.svg
+++ b/core/misc/icons/55565b/selection_list.svg
@@ -1,8 +1 @@
-<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5 6.5H18V8.5H5V6.5Z" fill="#55565B"/>
-<path d="M3 6H0V9H3V6Z" fill="#55565B"/>
-<path d="M18 12.5H5V14.5H18V12.5Z" fill="#55565B"/>
-<path d="M3 12H0V15H3V12Z" fill="#55565B"/>
-<path d="M18 0.5H5V2.5H18V0.5Z" fill="#55565B"/>
-<path d="M3 0H0V3H3V0Z" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m4.98 9.51v2.49h5.04v-4.98h-5.04zm7.02 0v1.47h19.98v-2.94h-19.98zm-7.02 9v2.49h5.04v-4.98h-5.04zm7.02-.03v1.5h19.98v-3h-19.98zm-7.02 9.03v2.49h5.04v-4.98h-5.04zm7.02 0v1.47h19.98v-2.94h-19.98z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/55565b/telephone.svg b/core/misc/icons/55565b/telephone.svg
index 32bb1a7b77cf6cfa7daac4cfd768eecf7977c790..b7f876bcad522d78710603f4d8333fb59628fcb7 100644
--- a/core/misc/icons/55565b/telephone.svg
+++ b/core/misc/icons/55565b/telephone.svg
@@ -1,3 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none">
-  <path d="M4.548 7.943a14.422 14.422 0 0 0 7.55 7.397c.552-.4.863-1.095 1.235-1.66.278-.275.834-.81 1.39-.55 1.381.65 2.04.877 3.61 1.1.155.021.579.164.834.19.383.037.833.36.833 1.136v3.07c0 .55-.451 1.282-1.033 1.323-.486.034-.882.051-1.19.051C7.918 20 0 11.862 0 2.198c0-.303.017-.696.052-1.176C.092.446.833 0 1.389 0h3.104c.785 0 1.11.445 1.149.824.025.252.17.672.191.825.225 1.552.455 2.205 1.111 3.572.265.55-.277 1.099-.555 1.374-.6.387-1.476.733-1.84 1.348Z" fill="#55565B"/>
-</svg>
+<svg height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m9.82246 14.9148c2.20964 4.9719 6.27314 8.9558 11.32454 11.0949.8278-.5984 1.2952-1.6427 1.853-2.4896.4167-.4122 1.25-1.2162 2.0833-.8243 2.0728.9746 3.0622 1.3152 5.4167 1.6486.2314.0328.8678.2463 1.25.2844.5749.0574 1.25.54 1.25 1.7046v4.6058c0 .8243-.6765 1.9233-1.5492 1.9844-.7289.0509-1.3237.0764-1.7841.0764-14.7917 0-26.6667-12.2065-26.6667-26.70265 0-.45554.02574-1.0438.07726-1.7649.0617-.86328 1.17274-1.53245 2.00607-1.53245h4.65592c1.17745 0 1.66525.66786 1.72325 1.23651.0386.37783.2543 1.00757.2875 1.23651.3371 2.32911.6814 3.30781 1.6667 5.35818.3961.8244-.4167 1.6487-.8334 2.0609-.9004.5803-2.2133 1.1-2.76084 2.0227z" fill="#55565b"/></svg>
\ No newline at end of file
diff --git a/core/misc/icons/cacbd2/puzzle_piece_placeholder.svg b/core/misc/icons/cacbd2/puzzle_piece_placeholder.svg
index 8c341ee4420f0d930c12f83ca26e941aa143e409..715531ee9dbc8f904c5d6995ae282905365f2a58 100644
--- a/core/misc/icons/cacbd2/puzzle_piece_placeholder.svg
+++ b/core/misc/icons/cacbd2/puzzle_piece_placeholder.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="22" height="21" fill="none"><path stroke="#CACBD2" stroke-width="2" d="M1 4.012h6c-.5-3.5 5-4.5 5 0h5v5c4-.5 6 6 0 6v4.5H1v-16.5"/></svg>
\ No newline at end of file
+<svg fill="none" height="36" viewBox="0 0 36 36" width="36" xmlns="http://www.w3.org/2000/svg"><path d="m5 7.71798 8.7937-.00001c-.7328-5.48304 7.3282-7.049628 7.3282 0l7.3281.00001v7.83292c5.8625-.7833 8.7937 9.3995 0 9.3995v7.0496h-23.45v-11.7494-14.02745" stroke="#cacbd2" stroke-width="3"/></svg>
\ No newline at end of file
diff --git a/core/modules/action/tests/src/Functional/GenericTest.php b/core/modules/action/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c67627892ddd133ff29874dec3fc26e6ffe4f01c
--- /dev/null
+++ b/core/modules/action/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\action\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for action.
+ *
+ * @group action
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/announcements_feed/tests/src/Functional/GenericTest.php b/core/modules/announcements_feed/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..63fa620029723007bfd1c43d6220c51f553538e6
--- /dev/null
+++ b/core/modules/announcements_feed/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\announcements_feed\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for announcements_feed.
+ *
+ * @group announcements_feed
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/automated_cron/tests/src/Functional/GenericTest.php b/core/modules/automated_cron/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e8edc0fc5e2a8f5b21c9bb115a139407bee616f
--- /dev/null
+++ b/core/modules/automated_cron/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\automated_cron\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for automated_cron.
+ *
+ * @group automated_cron
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/ban/src/BanIpManager.php b/core/modules/ban/src/BanIpManager.php
index 05fbbe8ee6fd3c78e42c1acecedeabcb59df90ce..42548a6528c44e82a13fc3b2c0d5d7be0b516b66 100644
--- a/core/modules/ban/src/BanIpManager.php
+++ b/core/modules/ban/src/BanIpManager.php
@@ -45,7 +45,7 @@ public function findAll() {
    */
   public function banIp($ip) {
     $this->connection->merge('ban_ip')
-      ->key(['ip' => $ip])
+      ->key('ip', $ip)
       ->fields(['ip' => $ip])
       ->execute();
   }
diff --git a/core/modules/ban/tests/src/Functional/GenericTest.php b/core/modules/ban/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a1ca6003f25dffef155c422774def90083e6c961
--- /dev/null
+++ b/core/modules/ban/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\ban\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for ban.
+ *
+ * @group ban
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/basic_auth/tests/src/Functional/GenericTest.php b/core/modules/basic_auth/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..678d1bed159808c9442ad812f651f614d56d7d0d
--- /dev/null
+++ b/core/modules/basic_auth/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\basic_auth\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for basic_auth.
+ *
+ * @group basic_auth
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/big_pipe/big_pipe.post_update.php b/core/modules/big_pipe/big_pipe.post_update.php
new file mode 100644
index 0000000000000000000000000000000000000000..c28880042f089e8cfb7c899752261d072286931c
--- /dev/null
+++ b/core/modules/big_pipe/big_pipe.post_update.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for Big Pipe.
+ */
+
+/**
+ * Clear the render cache.
+ */
+function big_pipe_post_update_html5_placeholders() {
+  // Empty post_update hook.
+}
diff --git a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php
index e10da9aa0c309d34a5636358a4b384eceaf2a822..602e1118ed810f7acb03f1347af817d0410e0ae0 100644
--- a/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php
+++ b/core/modules/big_pipe/tests/modules/big_pipe_test/src/BigPipePlaceholderTestCases.php
@@ -303,7 +303,7 @@ public static function cases(ContainerInterface $container = NULL, AccountInterf
         '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
         '#create_placeholder' => TRUE,
       ],
-      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::responseException" arguments="" token="' . $token . ' "></drupal-render-placeholder>',
+      '<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::responseException" arguments token="' . $token . ' "></drupal-render-placeholder>',
       [
         '#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
       ]
diff --git a/core/modules/big_pipe/tests/src/Functional/GenericTest.php b/core/modules/big_pipe/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b5aed9267635a737e46f261ba016f00762db70a
--- /dev/null
+++ b/core/modules/big_pipe/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\big_pipe\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for big_pipe.
+ *
+ * @group big_pipe
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/block/src/BlockRepository.php b/core/modules/block/src/BlockRepository.php
index 146155717a7bcfd8591fcc2114b0ab13e05b88ca..4103270d55b5db592ef530849df24c120a4083e2 100644
--- a/core/modules/block/src/BlockRepository.php
+++ b/core/modules/block/src/BlockRepository.php
@@ -78,9 +78,7 @@ public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []) {
     // Merge it with the actual values to maintain the region ordering.
     $assignments = array_intersect_key(array_merge($empty, $full), $empty);
     foreach ($assignments as &$assignment) {
-      // Suppress errors because PHPUnit will indirectly modify the contents,
-      // triggering https://bugs.php.net/bug.php?id=50688.
-      @uasort($assignment, 'Drupal\block\Entity\Block::sort');
+      uasort($assignment, 'Drupal\block\Entity\Block::sort');
     }
     return $assignments;
   }
diff --git a/core/modules/block/tests/src/Functional/GenericTest.php b/core/modules/block/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f69d0caed3a5fa3fb1271cd54a972d16e33c642e
--- /dev/null
+++ b/core/modules/block/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\block\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for block.
+ *
+ * @group block
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockContentTranslationTest.php b/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockContentTranslationTest.php
index 774a8ac999d7c287806708e98538a5899c00cf29..6c346aeac4f01f47ad93c38516b02c3490e75249 100644
--- a/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockContentTranslationTest.php
+++ b/core/modules/block/tests/src/Kernel/Migrate/d7/MigrateBlockContentTranslationTest.php
@@ -8,6 +8,7 @@
  * Tests migration of i18n block translations.
  *
  * @group migrate_drupal_7
+ * @group #slow
  */
 class MigrateBlockContentTranslationTest extends MigrateDrupal7TestBase {
 
@@ -59,6 +60,9 @@ protected function setUp(): void {
    * Tests the migration of block title translation.
    */
   public function testBlockContentTranslation() {
+    // @todo Skipped due to frequent random test failures.
+    // See https://www.drupal.org/project/drupal/issues/3389365
+    $this->markTestSkipped();
     /** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */
     $language_manager = $this->container->get('language_manager');
 
diff --git a/core/modules/block_content/src/BlockContentTypeForm.php b/core/modules/block_content/src/BlockContentTypeForm.php
index 2124518ddbfca84c9b7b49858dc19a3eaddd32dd..6044917f16b33bfe08abb00e2085b031234981aa 100644
--- a/core/modules/block_content/src/BlockContentTypeForm.php
+++ b/core/modules/block_content/src/BlockContentTypeForm.php
@@ -35,7 +35,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#title' => $this->t('Label'),
       '#maxlength' => 255,
       '#default_value' => $block_type->label(),
-      '#description' => $this->t("Provide a label for this block type to help identify it in the administration pages."),
+      '#description' => $this->t("The human-readable name for this block type, displayed on the <em>Block types</em> page."),
       '#required' => TRUE,
     ];
     $form['id'] = [
@@ -44,13 +44,14 @@ public function form(array $form, FormStateInterface $form_state) {
       '#machine_name' => [
         'exists' => '\Drupal\block_content\Entity\BlockContentType::load',
       ],
+      '#description' => $this->t("Unique machine-readable name: lowercase letters, numbers, and underscores only."),
       '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
     ];
 
     $form['description'] = [
       '#type' => 'textarea',
       '#default_value' => $block_type->getDescription(),
-      '#description' => $this->t('Enter a description for this block type.'),
+      '#description' => $this->t('Displays on the <em>Block types</em> page.'),
       '#title' => $this->t('Description'),
     ];
 
diff --git a/core/modules/block_content/src/Controller/BlockContentController.php b/core/modules/block_content/src/Controller/BlockContentController.php
index 4d4c8e4382f4e6e57455b3f42084d3549434f0c4..dbe844d9d6bb3f7cdfeb05a60becd6123f2fc624 100644
--- a/core/modules/block_content/src/Controller/BlockContentController.php
+++ b/core/modules/block_content/src/Controller/BlockContentController.php
@@ -157,7 +157,7 @@ public function getAddFormTitle(BlockContentTypeInterface $block_content_type) {
    * @see https://www.drupal.org/node/3320855
    */
   public function blockContentTypeRedirect(RouteMatchInterface $route_match, Request $request): RedirectResponse {
-    @trigger_error('The path /admin/structure/block/block-content/types is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/structure/block-content. See https://www.drupal.org/node/3320855.', E_USER_DEPRECATED);
+    @trigger_error('The path /admin/structure/block/block-content/types is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/structure/block-content. See https://www.drupal.org/node/3320855', E_USER_DEPRECATED);
     $helper = new PathChangedHelper($route_match, $request);
     $params = [
       '%old_path' => $helper->oldPath(),
@@ -188,7 +188,7 @@ public function blockContentTypeRedirect(RouteMatchInterface $route_match, Reque
    * @see https://www.drupal.org/node/3320855
    */
   public function blockLibraryRedirect(RouteMatchInterface $route_match, Request $request) {
-    @trigger_error('The path /admin/structure/block/block-content is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block. See https://www.drupal.org/node/3320855.', E_USER_DEPRECATED);
+    @trigger_error('The path /admin/structure/block/block-content is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block. See https://www.drupal.org/node/3320855', E_USER_DEPRECATED);
     $helper = new PathChangedHelper($route_match, $request);
     $params = [
       '%old_path' => $helper->oldPath(),
@@ -222,7 +222,7 @@ public function blockLibraryRedirect(RouteMatchInterface $route_match, Request $
    * @see https://www.drupal.org/node/3320855
    */
   public function editRedirect(RouteMatchInterface $route_match, Request $request, BlockContentInterface $block_content): RedirectResponse {
-    @trigger_error('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855.', E_USER_DEPRECATED);
+    @trigger_error('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855', E_USER_DEPRECATED);
     $helper = new PathChangedHelper($route_match, $request);
     $params = [
       '%old_path' => $helper->oldPath(),
diff --git a/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_revision_id.yml b/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_revision_id.yml
index 5c4f3cab61e3dd6631591164acb3308e984a6379..e0f00b6a92ca214aee470230a1499781fd253e5f 100644
--- a/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_revision_id.yml
+++ b/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_revision_id.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - block_content
 id: test_block_content_revision_id
-label: null
+label: test_block_content_revision_id
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_revision_revision_id.yml b/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_revision_revision_id.yml
index fca8157dc0e6167966dedccf6fd242dab6442379..5511addfd43a34eb06c253213ec2ea0a28af84f8 100644
--- a/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_revision_revision_id.yml
+++ b/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_block_content_revision_revision_id.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - block_content
 id: test_block_content_revision_revision_id
-label: null
+label: test_block_content_revision_revision_id
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_field_type.yml b/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_field_type.yml
index 6c29d842cf56a5a56a4b13a71294d9a239bb1ada..a3bc8828ab84d39f6742f8fbbf633156f253c6db 100644
--- a/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_field_type.yml
+++ b/core/modules/block_content/tests/modules/block_content_test_views/test_views/views.view.test_field_type.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - block_content
 id: test_field_type
-label: ''
+label: test_field_type
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/block_content/tests/src/Functional/BlockContentRedirectTest.php b/core/modules/block_content/tests/src/Functional/BlockContentRedirectTest.php
index ad1f9ca3d1e3cffaa316cff320ce5882a8007c86..434c4eefa1e8c60705c09f5ca39ce34ac6d2180c 100644
--- a/core/modules/block_content/tests/src/Functional/BlockContentRedirectTest.php
+++ b/core/modules/block_content/tests/src/Functional/BlockContentRedirectTest.php
@@ -22,7 +22,7 @@ class BlockContentRedirectTest extends BlockContentTestBase {
    */
   public function testBlockContentTypeRedirect() {
     $this->drupalLogin($this->adminUser);
-    $this->expectDeprecation('The path /admin/structure/block/block-content/types is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/structure/block-content. See https://www.drupal.org/node/3320855.');
+    $this->expectDeprecation('The path /admin/structure/block/block-content/types is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/structure/block-content. See https://www.drupal.org/node/3320855');
     $this->drupalGet('/admin/structure/block/block-content/types');
     $this->assertSession()
       ->pageTextContains("You have been redirected from admin/structure/block/block-content/types. Update links, shortcuts, and bookmarks to use admin/structure/block-content.");
@@ -35,7 +35,7 @@ public function testBlockContentTypeRedirect() {
    */
   public function testBlockLibraryRedirect() {
     $this->drupalLogin($this->adminUser);
-    $this->expectDeprecation('The path /admin/structure/block/block-content is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block. See https://www.drupal.org/node/3320855.');
+    $this->expectDeprecation('The path /admin/structure/block/block-content is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block. See https://www.drupal.org/node/3320855');
     $this->drupalGet('admin/structure/block/block-content');
     $this->assertSession()
       ->pageTextContains("You have been redirected from admin/structure/block/block-content. Update links, shortcuts, and bookmarks to use admin/content/block.");
@@ -49,7 +49,7 @@ public function testBlockLibraryRedirect() {
   public function testBlockContentEditRedirect(): void {
     $block = $this->createBlockContent();
     $this->drupalLogin($this->adminUser);
-    $this->expectDeprecation('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855.');
+    $this->expectDeprecation('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855');
     $this->drupalGet("/block/{$block->id()}");
     $this->assertSession()
       ->pageTextContains("You have been redirected from block/{$block->id()}. Update links, shortcuts, and bookmarks to use admin/content/block/{$block->id()}.");
@@ -63,7 +63,7 @@ public function testBlockContentEditRedirect(): void {
   public function testBlockContentDeleteRedirect(): void {
     $block = $this->createBlockContent();
     $this->drupalLogin($this->adminUser);
-    $this->expectDeprecation('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855.');
+    $this->expectDeprecation('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855');
     $this->drupalGet("/block/{$block->id()}/delete");
     $this->assertSession()
       ->pageTextContains("You have been redirected from block/{$block->id()}/delete. Update links, shortcuts, and bookmarks to use admin/content/block/{$block->id()}/delete.");
diff --git a/core/modules/block_content/tests/src/Functional/GenericTest.php b/core/modules/block_content/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..29c93a1708ea5bd294a9d37e528a53c34256a998
--- /dev/null
+++ b/core/modules/block_content/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\block_content\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for block_content.
+ *
+ * @group block_content
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/book/tests/src/Functional/BookTest.php b/core/modules/book/tests/src/Functional/BookTest.php
index 63a73cbec3fd14b31b28ee8769ca24d6849019eb..ffbc1ec1b41e33c62d95c5b6a6e67fdf0da7e046 100644
--- a/core/modules/book/tests/src/Functional/BookTest.php
+++ b/core/modules/book/tests/src/Functional/BookTest.php
@@ -10,6 +10,7 @@
  * Create a book, add pages, and test book interface.
  *
  * @group book
+ * @group #slow
  */
 class BookTest extends BrowserTestBase {
 
diff --git a/core/modules/book/tests/src/Functional/GenericTest.php b/core/modules/book/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9d88fcb4a9acfc635dc4cf2d56ae83ac5af5c96f
--- /dev/null
+++ b/core/modules/book/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\book\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for book.
+ *
+ * @group book
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/breakpoint/tests/src/Functional/GenericTest.php b/core/modules/breakpoint/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..355a09d550d5ddaa54f0017515d635e2edb035a3
--- /dev/null
+++ b/core/modules/breakpoint/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\breakpoint\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for breakpoint.
+ *
+ * @group breakpoint
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/ckeditor5/config/schema/ckeditor5.schema.yml b/core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
index 20c31399c4af7b728cd5f7c8f123578c45623eab..52a9e1afbddf8ea2eda18defd28ab06f01ccad88 100644
--- a/core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
+++ b/core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
@@ -41,10 +41,14 @@ ckeditor5.plugin.ckeditor5_language:
         # Configuring this does not make sense without the corresponding button.
         CKEditor5ToolbarItemDependencyConstraint:
           toolbarItem: textPartLanguage
-        # Only two possible values are accepted.
+        # Only the following values are accepted.
         Choice:
+          # United Nations "official languages".
           - un
+          # Drupal's predefined language list.
           - all
+          # Languages configured at /admin/config/regional/language for the site.
+          - site_configured
 
 # Plugin \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Heading
 ckeditor5.plugin.ckeditor5_heading:
diff --git a/core/modules/ckeditor5/js/build/drupalMedia.js b/core/modules/ckeditor5/js/build/drupalMedia.js
index 6b33280aed0cb132a34c29825454251119d10b71..f2a730c4ba993bc81772c98d285aafd22f52bc79 100644
--- a/core/modules/ckeditor5/js/build/drupalMedia.js
+++ b/core/modules/ckeditor5/js/build/drupalMedia.js
@@ -1 +1 @@
-!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CKEditor5=t():(e.CKEditor5=e.CKEditor5||{},e.CKEditor5.drupalMedia=t())}(globalThis,(()=>(()=>{var e={"ckeditor5/src/core.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/engine.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/engine.js")},"ckeditor5/src/ui.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/ui.js")},"ckeditor5/src/utils.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/utils.js")},"ckeditor5/src/widget.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/widget.js")},"dll-reference CKEditor5.dll":e=>{"use strict";e.exports=CKEditor5.dll}},t={};function i(n){var a=t[n];if(void 0!==a)return a.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,i),r.exports}i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n={};return(()=>{"use strict";i.d(n,{default:()=>ue});var e=i("ckeditor5/src/core.js"),t=i("ckeditor5/src/widget.js");function a(e){return!!e&&e.is("element","drupalMedia")}function r(e){return(0,t.isWidget)(e)&&!!e.getCustomProperty("drupalMedia")}function o(e){const t=e.getSelectedElement();return a(t)?t:e.getFirstPosition().findAncestor("drupalMedia")}function s(e){const t=e.getSelectedElement();if(t&&r(t))return t;if(null===e.getFirstPosition())return null;let i=e.getFirstPosition().parent;for(;i;){if(i.is("element")&&r(i))return i;i=i.parent}return null}function l(e){const t=typeof e;return null!=e&&("object"===t||"function"===t)}function d(e){for(const t of e){if(t.hasAttribute("data-drupal-media-preview"))return t;if(t.childCount){const e=d(t.getChildren());if(e)return e}}return null}function u(e){return`drupalElementStyle${e[0].toUpperCase()+e.substring(1)}`}class c extends e.Command{execute(e){const t=this.editor.plugins.get("DrupalMediaEditing"),i=Object.entries(t.attrs).reduce(((e,[t,i])=>(e[i]=t,e)),{}),n=Object.keys(e).reduce(((t,n)=>(i[n]&&(t[i[n]]=e[n]),t)),{});if(this.editor.plugins.has("DrupalElementStyleEditing")){const t=this.editor.plugins.get("DrupalElementStyleEditing"),{normalizedStyles:i}=t;for(const a of Object.keys(i))for(const i of t.normalizedStyles[a])if(e[i.attributeName]&&i.attributeValue===e[i.attributeName]){const e=u(a);n[e]=i.name}}this.editor.model.change((e=>{this.editor.model.insertContent(function(e,t){return e.createElement("drupalMedia",t)}(e,n))}))}refresh(){const e=this.editor.model,t=e.document.selection,i=e.schema.findAllowedParent(t.getFirstPosition(),"drupalMedia");this.isEnabled=null!==i}}const m="METADATA_ERROR";class p extends e.Plugin{static get requires(){return[t.Widget]}constructor(e){super(e),this.attrs={drupalMediaAlt:"alt",drupalMediaEntityType:"data-entity-type",drupalMediaEntityUuid:"data-entity-uuid"},this.converterAttributes=["drupalMediaEntityUuid","drupalElementStyleViewMode","drupalMediaEntityType","drupalMediaAlt"]}init(){const e=this.editor.config.get("drupalMedia");if(!e)return;const{previewURL:t,themeError:i}=e;this.previewUrl=t,this.labelError=Drupal.t("Preview failed"),this.themeError=i||`\n      <p>${Drupal.t("An error occurred while trying to preview the media. Save your work and reload this page.")}<p>\n    `,this._defineSchema(),this._defineConverters(),this._defineListeners(),this.editor.commands.add("insertDrupalMedia",new c(this.editor))}upcastDrupalMediaIsImage(e){const{model:t,plugins:i}=this.editor;i.get("DrupalMediaMetadataRepository").getMetadata(e).then((i=>{e&&t.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaIsImage",!!i.imageSourceMetadata,e)}))})).catch((i=>{e&&(console.warn(i.toString()),t.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaIsImage",m,e)})))}))}upcastDrupalMediaType(e){this.editor.plugins.get("DrupalMediaMetadataRepository").getMetadata(e).then((t=>{e&&this.editor.model.enqueueChange({isUndoable:!1},(i=>{i.setAttribute("drupalMediaType",t.type,e)}))})).catch((t=>{e&&(console.warn(t.toString()),this.editor.model.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaType",m,e)})))}))}async _fetchPreview(e){const t={text:this._renderElement(e),uuid:e.getAttribute("drupalMediaEntityUuid")},i=await fetch(`${this.previewUrl}?${new URLSearchParams(t)}`,{headers:{"X-Drupal-MediaPreview-CSRF-Token":this.editor.config.get("drupalMedia").previewCsrfToken}});if(i.ok){return{label:i.headers.get("drupal-media-label"),preview:await i.text()}}return{label:this.labelError,preview:this.themeError}}_defineSchema(){this.editor.model.schema.register("drupalMedia",{allowWhere:"$block",isObject:!0,isContent:!0,isBlock:!0,allowAttributes:Object.keys(this.attrs)}),this.editor.editing.view.domConverter.blockElements.push("drupal-media")}_defineConverters(){const e=this.editor.conversion,i=this.editor.plugins.get("DrupalMediaMetadataRepository");e.for("upcast").elementToElement({view:{name:"drupal-media"},model:"drupalMedia"}).add((e=>{e.on("element:drupal-media",((e,t)=>{const[n]=t.modelRange.getItems();i.getMetadata(n).then((e=>{n&&(this.upcastDrupalMediaIsImage(n),this.editor.model.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaType",e.type,n)})))})).catch((e=>{console.warn(e.toString())}))}),{priority:"lowest"})})),e.for("dataDowncast").elementToElement({model:"drupalMedia",view:{name:"drupal-media"}}),e.for("editingDowncast").elementToElement({model:"drupalMedia",view:(e,{writer:i})=>{const n=i.createContainerElement("figure",{class:"drupal-media"});if(!this.previewUrl){const e=i.createRawElement("div",{"data-drupal-media-preview":"unavailable"});i.insert(i.createPositionAt(n,0),e)}return i.setCustomProperty("drupalMedia",!0,n),(0,t.toWidget)(n,i,{label:Drupal.t("Media widget")})}}).add((e=>{const t=(e,t,i)=>{const n=i.writer,a=t.item,r=i.mapper.toViewElement(t.item);let o=d(r.getChildren());if(o){if("ready"!==o.getAttribute("data-drupal-media-preview"))return;n.setAttribute("data-drupal-media-preview","loading",o)}else o=n.createRawElement("div",{"data-drupal-media-preview":"loading"}),n.insert(n.createPositionAt(r,0),o);this._fetchPreview(a).then((({label:e,preview:t})=>{o&&this.editor.editing.view.change((i=>{const n=i.createRawElement("div",{"data-drupal-media-preview":"ready","aria-label":e},(e=>{e.innerHTML=t}));i.insert(i.createPositionBefore(o),n),i.remove(o)}))}))};return this.converterAttributes.forEach((i=>{e.on(`attribute:${i}:drupalMedia`,t)})),e})),e.for("editingDowncast").add((e=>{e.on("attribute:drupalElementStyleAlign:drupalMedia",((e,t,i)=>{const n={left:"drupal-media-style-align-left",right:"drupal-media-style-align-right",center:"drupal-media-style-align-center"},a=i.mapper.toViewElement(t.item),r=i.writer;n[t.attributeOldValue]&&r.removeClass(n[t.attributeOldValue],a),n[t.attributeNewValue]&&i.consumable.consume(t.item,e.name)&&r.addClass(n[t.attributeNewValue],a)}))})),Object.keys(this.attrs).forEach((t=>{const i={model:{key:t,name:"drupalMedia"},view:{name:"drupal-media",key:this.attrs[t]}};e.for("dataDowncast").attributeToAttribute(i),e.for("upcast").attributeToAttribute(i)}))}_defineListeners(){this.editor.model.on("insertContent",((e,[t])=>{a(t)&&(this.upcastDrupalMediaIsImage(t),this.upcastDrupalMediaType(t))}))}_renderElement(e){const t=this.editor.model.change((t=>{const i=t.createDocumentFragment(),n=t.cloneElement(e,!1);return["linkHref"].forEach((e=>{t.removeAttribute(e,n)})),t.append(n,i),i}));return this.editor.data.stringify(t)}static get pluginName(){return"DrupalMediaEditing"}}var g=i("ckeditor5/src/ui.js");class h extends e.Plugin{init(){const e=this.editor,t=this.editor.config.get("drupalMedia");if(!t)return;const{libraryURL:i,openDialog:n,dialogSettings:a={}}=t;i&&"function"==typeof n&&e.ui.componentFactory.add("drupalMedia",(t=>{const r=e.commands.get("insertDrupalMedia"),o=new g.ButtonView(t);return o.set({label:Drupal.t("Insert Media"),icon:'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.1873 4.86414L10.2509 6.86414V7.02335H10.2499V15.5091C9.70972 15.1961 9.01793 15.1048 8.34069 15.3136C7.12086 15.6896 6.41013 16.8967 6.75322 18.0096C7.09631 19.1226 8.3633 19.72 9.58313 19.344C10.6666 19.01 11.3484 18.0203 11.2469 17.0234H11.2499V9.80173L18.1803 8.25067V14.3868C17.6401 14.0739 16.9483 13.9825 16.2711 14.1913C15.0513 14.5674 14.3406 15.7744 14.6836 16.8875C15.0267 18.0004 16.2937 18.5978 17.5136 18.2218C18.597 17.8877 19.2788 16.8982 19.1773 15.9011H19.1803V8.02687L19.1873 8.0253V4.86414Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13.5039 0.743652H0.386932V12.1603H13.5039V0.743652ZM12.3379 1.75842H1.55289V11.1454H1.65715L4.00622 8.86353L6.06254 10.861L9.24985 5.91309L11.3812 9.22179L11.7761 8.6676L12.3379 9.45621V1.75842ZM6.22048 4.50869C6.22048 5.58193 5.35045 6.45196 4.27722 6.45196C3.20398 6.45196 2.33395 5.58193 2.33395 4.50869C2.33395 3.43546 3.20398 2.56543 4.27722 2.56543C5.35045 2.56543 6.22048 3.43546 6.22048 4.50869Z"/></svg>\n',tooltip:!0}),o.bind("isOn","isEnabled").to(r,"value","isEnabled"),this.listenTo(o,"execute",(()=>{n(i,(({attributes:t})=>{e.execute("insertDrupalMedia",t)}),a)})),o}))}}class f extends e.Plugin{static get requires(){return[t.WidgetToolbarRepository]}static get pluginName(){return"DrupalMediaToolbar"}afterInit(){const{editor:e}=this;var i;e.plugins.get(t.WidgetToolbarRepository).register("drupalMedia",{ariaLabel:Drupal.t("Drupal Media toolbar"),items:(i=e.config.get("drupalMedia.toolbar"),i.map((e=>l(e)?e.name:e))||[]),getRelatedElement:e=>s(e)})}}class b extends e.Command{refresh(){const e=o(this.editor.model.document.selection);this.isEnabled=!!e&&e.getAttribute("drupalMediaIsImage")&&e.getAttribute("drupalMediaIsImage")!==m,this.isEnabled?this.value=e.getAttribute("drupalMediaAlt"):this.value=!1}execute(e){const{model:t}=this.editor,i=o(t.document.selection);e.newValue=e.newValue.trim(),t.change((t=>{e.newValue.length>0?t.setAttribute("drupalMediaAlt",e.newValue,i):t.removeAttribute("drupalMediaAlt",i)}))}}class w extends e.Plugin{init(){this._data=new WeakMap}getMetadata(e){if(this._data.get(e))return new Promise((t=>{t(this._data.get(e))}));const t=this.editor.config.get("drupalMedia");if(!t)return new Promise(((e,t)=>{t(new Error("drupalMedia configuration is required for parsing metadata."))}));if(!e.hasAttribute("drupalMediaEntityUuid"))return new Promise(((e,t)=>{t(new Error("drupalMedia element must have drupalMediaEntityUuid attribute to retrieve metadata."))}));const{metadataUrl:i}=t;return(async e=>{const t=await fetch(e);if(t.ok)return JSON.parse(await t.text());throw new Error("Fetching media embed metadata from the server failed.")})(`${i}&${new URLSearchParams({uuid:e.getAttribute("drupalMediaEntityUuid")})}`).then((t=>(this._data.set(e,t),t)))}static get pluginName(){return"DrupalMediaMetadataRepository"}}class y extends e.Plugin{static get requires(){return[w]}static get pluginName(){return"MediaImageTextAlternativeEditing"}init(){const{editor:e,editor:{model:t,conversion:i}}=this;t.schema.extend("drupalMedia",{allowAttributes:["drupalMediaIsImage"]}),i.for("editingDowncast").add((e=>{e.on("attribute:drupalMediaIsImage",((e,t,i)=>{const{writer:n,mapper:a}=i,r=a.toViewElement(t.item);if(t.attributeNewValue!==m){const e=Array.from(r.getChildren()).find((e=>e.getCustomProperty("drupalMediaMetadataError")));return void(e&&(n.setCustomProperty("widgetLabel",e.getCustomProperty("drupalMediaOriginalWidgetLabel"),e),n.removeElement(e)))}const o=Drupal.t("Not all functionality may be available because some information could not be retrieved."),s=new g.Template({tag:"span",children:[{tag:"span",attributes:{class:"drupal-media__metadata-error-icon","data-cke-tooltip-text":o}}]}).render(),l=n.createRawElement("div",{class:"drupal-media__metadata-error"},((e,t)=>{t.setContentOf(e,s.outerHTML)}));n.setCustomProperty("drupalMediaMetadataError",!0,l);const d=r.getCustomProperty("widgetLabel");n.setCustomProperty("drupalMediaOriginalWidgetLabel",d,l),n.setCustomProperty("widgetLabel",`${d} (${o})`,r),n.insert(n.createPositionAt(r,0),l)}),{priority:"low"})})),e.commands.add("mediaImageTextAlternative",new b(this.editor))}}function v(e){const t=e.editing.view,i=g.BalloonPanelView.defaultPositions;return{target:t.domConverter.viewToDom(t.document.selection.getSelectedElement()),positions:[i.northArrowSouth,i.northArrowSouthWest,i.northArrowSouthEast,i.southArrowNorth,i.southArrowNorthWest,i.southArrowNorthEast]}}var E=i("ckeditor5/src/utils.js");class M extends g.View{constructor(t){super(t),this.focusTracker=new E.FocusTracker,this.keystrokes=new E.KeystrokeHandler,this.labeledInput=this._createLabeledInputView(),this.set("defaultAltText",void 0),this.defaultAltTextView=this._createDefaultAltTextView(),this.saveButtonView=this._createButton(Drupal.t("Save"),e.icons.check,"ck-button-save"),this.saveButtonView.type="submit",this.cancelButtonView=this._createButton(Drupal.t("Cancel"),e.icons.cancel,"ck-button-cancel","cancel"),this._focusables=new g.ViewCollection,this._focusCycler=new g.FocusCycler({focusables:this._focusables,focusTracker:this.focusTracker,keystrokeHandler:this.keystrokes,actions:{focusPrevious:"shift + tab",focusNext:"tab"}}),this.setTemplate({tag:"form",attributes:{class:["ck","ck-media-alternative-text-form","ck-vertical-form"],tabindex:"-1"},children:[this.defaultAltTextView,this.labeledInput,this.saveButtonView,this.cancelButtonView]}),(0,g.injectCssTransitionDisabler)(this)}render(){super.render(),this.keystrokes.listenTo(this.element),(0,g.submitHandler)({view:this}),[this.labeledInput,this.saveButtonView,this.cancelButtonView].forEach((e=>{this._focusables.add(e),this.focusTracker.add(e.element)}))}_createButton(e,t,i,n){const a=new g.ButtonView(this.locale);return a.set({label:e,icon:t,tooltip:!0}),a.extendTemplate({attributes:{class:i}}),n&&a.delegate("execute").to(this,n),a}_createLabeledInputView(){const e=new g.LabeledFieldView(this.locale,g.createLabeledInputText);return e.label=Drupal.t("Alternative text override"),e}_createDefaultAltTextView(){const e=g.Template.bind(this,this);return new g.Template({tag:"div",attributes:{class:["ck-media-alternative-text-form__default-alt-text",e.if("defaultAltText","ck-hidden",(e=>!e))]},children:[{tag:"strong",attributes:{class:"ck-media-alternative-text-form__default-alt-text-label"},children:[Drupal.t("Default alternative text:")]}," ",{tag:"span",attributes:{class:"ck-media-alternative-text-form__default-alt-text-value"},children:[{text:[e.to("defaultAltText")]}]}]})}}class k extends e.Plugin{static get requires(){return[g.ContextualBalloon]}static get pluginName(){return"MediaImageTextAlternativeUi"}init(){this._createButton(),this._createForm()}destroy(){super.destroy(),this._form.destroy()}_createButton(){const t=this.editor;t.ui.componentFactory.add("mediaImageTextAlternative",(i=>{const n=t.commands.get("mediaImageTextAlternative"),a=new g.ButtonView(i);return a.set({label:Drupal.t("Override media image alternative text"),icon:e.icons.lowVision,tooltip:!0}),a.bind("isVisible").to(n,"isEnabled"),this.listenTo(a,"execute",(()=>{this._showForm()})),a}))}_createForm(){const e=this.editor,t=e.editing.view.document;this._balloon=this.editor.plugins.get("ContextualBalloon"),this._form=new M(e.locale),this._form.render(),this.listenTo(this._form,"submit",(()=>{e.execute("mediaImageTextAlternative",{newValue:this._form.labeledInput.fieldView.element.value}),this._hideForm(!0)})),this.listenTo(this._form,"cancel",(()=>{this._hideForm(!0)})),this._form.keystrokes.set("Esc",((e,t)=>{this._hideForm(!0),t()})),this.listenTo(e.ui,"update",(()=>{s(t.selection)?this._isVisible&&function(e){const t=e.plugins.get("ContextualBalloon");if(s(e.editing.view.document.selection)){const i=v(e);t.updatePosition(i)}}(e):this._hideForm(!0)})),(0,g.clickOutsideHandler)({emitter:this._form,activator:()=>this._isVisible,contextElements:[this._balloon.view.element],callback:()=>this._hideForm()})}_showForm(){if(this._isVisible)return;const e=this.editor,t=e.commands.get("mediaImageTextAlternative"),i=e.plugins.get("DrupalMediaMetadataRepository"),n=this._form.labeledInput;this._form.disableCssTransitions(),this._isInBalloon||this._balloon.add({view:this._form,position:v(e)}),n.fieldView.element.value=t.value||"",n.fieldView.value=n.fieldView.element.value,this._form.defaultAltText="";const r=e.model.document.selection.getSelectedElement();a(r)&&i.getMetadata(r).then((e=>{this._form.defaultAltText=e.imageSourceMetadata?e.imageSourceMetadata.alt:""})).catch((e=>{console.warn(e.toString())})),this._form.labeledInput.fieldView.select(),this._form.enableCssTransitions()}_hideForm(e){this._isInBalloon&&(this._form.focusTracker.isFocused&&this._form.saveButtonView.focus(),this._balloon.remove(this._form),e&&this.editor.editing.view.focus())}get _isVisible(){return this._balloon.visibleView===this._form}get _isInBalloon(){return this._balloon.hasView(this._form)}}class A extends e.Plugin{static get requires(){return[y,k]}static get pluginName(){return"MediaImageTextAlternative"}}function D(e,t,i){if(t.attributes)for(const[n,a]of Object.entries(t.attributes))e.setAttribute(n,a,i);t.styles&&e.setStyle(t.styles,i),t.classes&&e.addClass(t.classes,i)}function C(e,t,i){if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item);D(i.writer,t.attributeNewValue,n)}class _ extends e.Plugin{constructor(e){if(super(e),!e.plugins.has("GeneralHtmlSupport"))return;e.plugins.has("DataFilter")&&e.plugins.has("DataSchema")||console.error("DataFilter and DataSchema plugins are required for Drupal Media to integrate with General HTML Support plugin.");const{schema:t}=e.model,{conversion:i}=e,n=this.editor.plugins.get("DataFilter");this.editor.plugins.get("DataSchema").registerBlockElement({model:"drupalMedia",view:"drupal-media"}),n.on("register:drupal-media",((e,a)=>{"drupalMedia"===a.model&&(t.extend("drupalMedia",{allowAttributes:["htmlLinkAttributes","htmlAttributes"]}),i.for("upcast").add(function(e){return t=>{t.on("element:drupal-media",((t,i,n)=>{function a(t,a){const r=e.processViewAttributes(t,n);r&&n.writer.setAttribute(a,r,i.modelRange)}const r=i.viewItem,o=r.parent;a(r,"htmlAttributes"),o.is("element","a")&&a(o,"htmlLinkAttributes")}),{priority:"low"})}}(n)),i.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const n=i.mapper.toViewElement(t.item),a=function(e,t,i){const n=e.createRangeOn(t);for(const{item:e}of n.getWalker())if(e.is("element",i))return e}(i.writer,n,"a");D(i.writer,t.item.getAttribute("htmlLinkAttributes"),a)}),{priority:"low"})})),i.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const n=i.mapper.toViewElement(t.item).parent;D(i.writer,t.item.getAttribute("htmlLinkAttributes"),n)}),{priority:"low"}),e.on("attribute:htmlAttributes:drupalMedia",C,{priority:"low"})})),e.stop())}))}static get pluginName(){return"DrupalMediaGeneralHtmlSupport"}}class x extends e.Plugin{static get requires(){return[p,_,h,f,A]}static get pluginName(){return"DrupalMedia"}}var V=i("ckeditor5/src/engine.js");function S(e){return Array.from(e.getChildren()).find((e=>"drupal-media"===e.name))}function T(e){return t=>{t.on(`attribute:${e.id}:drupalMedia`,((t,i,n)=>{const a=n.mapper.toViewElement(i.item);let r=Array.from(a.getChildren()).find((e=>"a"===e.name));if(r=!r&&a.is("element","a")?a:Array.from(a.getAncestors()).find((e=>"a"===e.name)),r){for(const[t,i]of(0,E.toMap)(e.attributes))n.writer.setAttribute(t,i,r);e.classes&&n.writer.addClass(e.classes,r);for(const t in e.styles)Object.prototype.hasOwnProperty.call(e.styles,t)&&n.writer.setStyle(t,e.styles[t],r)}}))}}function I(e,t){return e=>{e.on("element:a",((e,i,n)=>{const a=i.viewItem;if(!S(a))return;const r=new V.Matcher(t._createPattern()).match(a);if(!r)return;if(!n.consumable.consume(a,r.match))return;const o=i.modelCursor.nodeBefore;n.writer.setAttribute(t.id,!0,o)}),{priority:"high"})}}class L extends e.Plugin{static get requires(){return["LinkEditing","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaEditing"}init(){const{editor:e}=this;e.model.schema.extend("drupalMedia",{allowAttributes:["linkHref"]}),e.conversion.for("upcast").add((e=>{e.on("element:a",((e,t,i)=>{const n=t.viewItem,a=S(n);if(!a)return;if(!i.consumable.consume(n,{attributes:["href"],name:!0}))return;const r=n.getAttribute("href");if(!r)return;const o=i.convertItem(a,t.modelCursor);t.modelRange=o.modelRange,t.modelCursor=o.modelCursor;const s=t.modelCursor.nodeBefore;s&&s.is("element","drupalMedia")&&i.writer.setAttribute("linkHref",r,s)}),{priority:"high"})})),e.conversion.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:n}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),r=Array.from(a.getChildren()).find((e=>"a"===e.name));if(r)t.attributeNewValue?n.setAttribute("href",t.attributeNewValue,r):(n.move(n.createRangeIn(r),n.createPositionAt(a,0)),n.remove(r));else{const e=Array.from(a.getChildren()).find((e=>e.getAttribute("data-drupal-media-preview"))),i=n.createContainerElement("a",{href:t.attributeNewValue});n.insert(n.createPositionAt(a,0),i),n.move(n.createRangeOn(e),n.createPositionAt(i,0))}}),{priority:"high"})})),e.conversion.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:n}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),r=n.createContainerElement("a",{href:t.attributeNewValue});n.insert(n.createPositionBefore(a),r),n.move(n.createRangeOn(a),n.createPositionAt(r,0))}),{priority:"high"})})),this._enableManualDecorators();if(e.commands.get("link").automaticDecorators.length>0)throw new Error("The Drupal Media plugin is not compatible with automatic link decorators. To use Drupal Media, disable any plugins providing automatic link decorators.")}_enableManualDecorators(){const e=this.editor,t=e.commands.get("link");for(const i of t.manualDecorators)e.model.schema.extend("drupalMedia",{allowAttributes:i.id}),e.conversion.for("downcast").add(T(i)),e.conversion.for("upcast").add(I(0,i))}}class P extends e.Plugin{static get requires(){return["LinkEditing","LinkUI","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaUi"}init(){const{editor:e}=this,t=e.editing.view.document;this.listenTo(t,"click",((t,i)=>{this._isSelectedLinkedMedia(e.model.document.selection)&&(i.preventDefault(),t.stop())}),{priority:"high"}),this._createToolbarLinkMediaButton()}_createToolbarLinkMediaButton(){const{editor:e}=this;e.ui.componentFactory.add("drupalLinkMedia",(t=>{const i=new g.ButtonView(t),n=e.plugins.get("LinkUI"),a=e.commands.get("link");return i.set({isEnabled:!0,label:Drupal.t("Link media"),icon:'<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m11.077 15 .991-1.416a.75.75 0 1 1 1.229.86l-1.148 1.64a.748.748 0 0 1-.217.206 5.251 5.251 0 0 1-8.503-5.955.741.741 0 0 1 .12-.274l1.147-1.639a.75.75 0 1 1 1.228.86L4.933 10.7l.006.003a3.75 3.75 0 0 0 6.132 4.294l.006.004zm5.494-5.335a.748.748 0 0 1-.12.274l-1.147 1.639a.75.75 0 1 1-1.228-.86l.86-1.23a3.75 3.75 0 0 0-6.144-4.301l-.86 1.229a.75.75 0 0 1-1.229-.86l1.148-1.64a.748.748 0 0 1 .217-.206 5.251 5.251 0 0 1 8.503 5.955zm-4.563-2.532a.75.75 0 0 1 .184 1.045l-3.155 4.505a.75.75 0 1 1-1.229-.86l3.155-4.506a.75.75 0 0 1 1.045-.184z"/></svg>\n',keystroke:"Ctrl+K",tooltip:!0,isToggleable:!0}),i.bind("isEnabled").to(a,"isEnabled"),i.bind("isOn").to(a,"value",(e=>!!e)),this.listenTo(i,"execute",(()=>{this._isSelectedLinkedMedia(e.model.document.selection)?n._addActionsView():n._showUI(!0)})),i}))}_isSelectedLinkedMedia(e){const t=e.getSelectedElement();return!!t&&t.is("element","drupalMedia")&&t.hasAttribute("linkHref")}}class B extends e.Plugin{static get requires(){return[L,P]}static get pluginName(){return"DrupalLinkMedia"}}const{objectFullWidth:O,objectInline:N,objectLeft:R,objectRight:j,objectCenter:F,objectBlockLeft:U,objectBlockRight:H}=e.icons,$={get inline(){return{name:"inline",title:"In line",icon:N,modelElements:["imageInline"],isDefault:!0}},get alignLeft(){return{name:"alignLeft",title:"Left aligned image",icon:R,modelElements:["imageBlock","imageInline"],className:"image-style-align-left"}},get alignBlockLeft(){return{name:"alignBlockLeft",title:"Left aligned image",icon:U,modelElements:["imageBlock"],className:"image-style-block-align-left"}},get alignCenter(){return{name:"alignCenter",title:"Centered image",icon:F,modelElements:["imageBlock"],className:"image-style-align-center"}},get alignRight(){return{name:"alignRight",title:"Right aligned image",icon:j,modelElements:["imageBlock","imageInline"],className:"image-style-align-right"}},get alignBlockRight(){return{name:"alignBlockRight",title:"Right aligned image",icon:H,modelElements:["imageBlock"],className:"image-style-block-align-right"}},get block(){return{name:"block",title:"Centered image",icon:F,modelElements:["imageBlock"],isDefault:!0}},get side(){return{name:"side",title:"Side image",icon:j,modelElements:["imageBlock"],className:"image-style-side"}}},q={full:O,left:U,right:H,center:F,inlineLeft:R,inlineRight:j,inline:N},W=[{name:"imageStyle:wrapText",title:"Wrap text",defaultItem:"imageStyle:alignLeft",items:["imageStyle:alignLeft","imageStyle:alignRight"]},{name:"imageStyle:breakText",title:"Break text",defaultItem:"imageStyle:block",items:["imageStyle:alignBlockLeft","imageStyle:block","imageStyle:alignBlockRight"]}];function K(e){(0,E.logWarning)("image-style-configuration-definition-invalid",e)}const z={normalizeStyles:function(e){return(e.configuredStyles.options||[]).map((e=>function(e){e="string"==typeof e?$[e]?{...$[e]}:{name:e}:function(e,t){const i={...t};for(const n in e)Object.prototype.hasOwnProperty.call(t,n)||(i[n]=e[n]);return i}($[e.name],e);"string"==typeof e.icon&&(e.icon=q[e.icon]||e.icon);return e}(e))).filter((t=>function(e,{isBlockPluginLoaded:t,isInlinePluginLoaded:i}){const{modelElements:n,name:a}=e;if(!(n&&n.length&&a))return K({style:e}),!1;{const a=[t?"imageBlock":null,i?"imageInline":null];if(!n.some((e=>a.includes(e))))return(0,E.logWarning)("image-style-missing-dependency",{style:e,missingPlugins:n.map((e=>"imageBlock"===e?"ImageBlockEditing":"ImageInlineEditing"))}),!1}return!0}(t,e)))},getDefaultStylesConfiguration:function(e,t){return e&&t?{options:["inline","alignLeft","alignRight","alignCenter","alignBlockLeft","alignBlockRight","block","side"]}:e?{options:["block","side"]}:t?{options:["inline","alignLeft","alignRight"]}:{}},getDefaultDropdownDefinitions:function(e){return e.has("ImageBlockEditing")&&e.has("ImageInlineEditing")?[...W]:[]},warnInvalidStyle:K,DEFAULT_OPTIONS:$,DEFAULT_ICONS:q,DEFAULT_DROPDOWN_DEFINITIONS:W};function Z(e,t,i){for(const n of t)if(i.checkAttribute(e,n))return!0;return!1}function G(e,t,i){const n=e.getSelectedElement();if(n&&Z(n,i,t))return n;let{parent:a}=e.getFirstPosition();for(;a;){if(a.is("element")&&Z(a,i,t))return a;a=a.parent}return null}class J extends e.Command{constructor(e,t){super(e),this.styles={},Object.keys(t).forEach((e=>{this.styles[e]=new Map(t[e].map((e=>[e.name,e])))})),this.modelAttributes=[];for(const e of Object.keys(t)){const t=u(e);this.modelAttributes.push(t)}}refresh(){const{editor:e}=this,t=G(e.model.document.selection,e.model.schema,this.modelAttributes);this.isEnabled=!!t,this.isEnabled?this.value=this.getValue(t):this.value=!1}getValue(e){const t={};return Object.keys(this.styles).forEach((i=>{const n=u(i);if(e.hasAttribute(n))t[i]=e.getAttribute(n);else for(const[,e]of this.styles[i])e.isDefault&&(t[i]=e.name)})),t}execute(e={}){const{editor:{model:t}}=this,{value:i,group:n}=e,a=u(n);t.change((e=>{const r=G(t.document.selection,t.schema,this.modelAttributes);!i||this.styles[n].get(i).isDefault?e.removeAttribute(a,r):e.setAttribute(a,i,r)}))}}function X(e,t){for(const i of t)if(i.name===e)return i}class Q extends e.Plugin{init(){const{editor:t}=this,i=t.config.get("drupalElementStyles");this.normalizedStyles={},Object.keys(i).forEach((t=>{this.normalizedStyles[t]=i[t].map((t=>("string"==typeof t.icon&&e.icons[t.icon]&&(t.icon=e.icons[t.icon]),t.name&&(t.name=`${t.name}`),t))).filter((e=>e.isDefault||e.attributeName&&e.attributeValue?e.modelElements&&Array.isArray(e.modelElements)?!!e.name||(console.warn("drupalElementStyles options must include a name."),!1):(console.warn("drupalElementStyles options must include an array of supported modelElements."),!1):(console.warn(`${e.attributeValue} drupalElementStyles options must include attributeName and attributeValue.`),!1)))})),this._setupConversion(),t.commands.add("drupalElementStyle",new J(t,this.normalizedStyles))}_setupConversion(){const{editor:e}=this,{schema:t}=e.model;Object.keys(this.normalizedStyles).forEach((i=>{const n=u(i),a=(r=this.normalizedStyles[i],(e,t,i)=>{if(!i.consumable.consume(t.item,e.name))return;const n=X(t.attributeNewValue,r),a=X(t.attributeOldValue,r),o=i.mapper.toViewElement(t.item),s=i.writer;a&&("class"===a.attributeName?s.removeClass(a.attributeValue,o):s.removeAttribute(a.attributeName,o)),n&&("class"===n.attributeName?s.addClass(n.attributeValue,o):n.isDefault||s.setAttribute(n.attributeName,n.attributeValue,o))});var r;const o=function(e,t){const i=e.filter((e=>!e.isDefault));return(e,n,a)=>{if(!n.modelRange)return;const r=n.viewItem,o=(0,E.first)(n.modelRange.getItems());if(o&&a.schema.checkAttribute(o,t))for(const e of i)if("class"===e.attributeName)a.consumable.consume(r,{classes:e.attributeValue})&&a.writer.setAttribute(t,e.name,o);else if(a.consumable.consume(r,{attributes:[e.attributeName]}))for(const e of i)e.attributeValue===r.getAttribute(e.attributeName)&&a.writer.setAttribute(t,e.name,o)}}(this.normalizedStyles[i],n);e.editing.downcastDispatcher.on(`attribute:${n}`,a),e.data.downcastDispatcher.on(`attribute:${n}`,a);[...new Set(this.normalizedStyles[i].map((e=>e.modelElements)).flat())].forEach((e=>{t.extend(e,{allowAttributes:n})})),e.data.upcastDispatcher.on("element",o,{priority:"low"})}))}static get pluginName(){return"DrupalElementStyleEditing"}}const Y=e=>e,ee=(e,t)=>(e?`${e}: `:"")+t;function te(e,t){return`drupalElementStyle:${t}:${e}`}class ie extends e.Plugin{static get requires(){return[Q]}init(){const{plugins:e}=this.editor,t=this.editor.config.get("drupalMedia.toolbar")||[],i=e.get("DrupalElementStyleEditing").normalizedStyles;Object.keys(i).forEach((e=>{i[e].forEach((t=>{this._createButton(t,e,i[e])}))}));t.filter(l).filter((e=>{const t=[];if(!e.display)return console.warn("dropdown configuration must include a display key specifying either listDropdown or splitButton."),!1;e.items.includes(e.defaultItem)||console.warn("defaultItem must be part of items in the dropdown configuration.");for(const i of e.items){const e=i.split(":")[1];t.push(e)}return!!t.every((e=>e===t[0]))||(console.warn("dropdown configuration should only contain buttons from one group."),!1)})).forEach((e=>{if(e.items.length>=2){const t=e.name.split(":")[1];switch(e.display){case"splitButton":this._createDropdown(e,i[t]);break;case"listDropdown":this._createListDropdown(e,i[t])}}}))}updateOptionVisibility(e,t,i,n){const{selection:a}=this.editor.model.document,r={};r[n]=e;const o=a?a.getSelectedElement():G(a,this.editor.model.schema,r),s=e.filter((function(e){for(const[t,i]of(0,E.toMap)(e.modelAttributes))if(o&&o.hasAttribute(t))return i.includes(o.getAttribute(t));return!0}));i.hasOwnProperty("model")?s.includes(t)?i.model.set({class:""}):i.model.set({class:"ck-hidden"}):s.includes(t)?i.set({class:""}):i.set({class:"ck-hidden"})}_createDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(n=>{let a;const{defaultItem:r,items:o,title:s}=e,l=o.filter((e=>{const i=e.split(":")[1];return t.find((({name:t})=>te(t,i)===e))})).map((e=>{const t=i.create(e);return e===r&&(a=t),t}));o.length!==l.length&&z.warnInvalidStyle({dropdown:e});const d=(0,g.createDropdown)(n,g.SplitButtonView),u=d.buttonView;return(0,g.addToolbarToDropdown)(d,l),u.set({label:ee(s,a.label),class:null,tooltip:!0}),u.bind("icon").toMany(l,"isOn",((...e)=>{const t=e.findIndex(Y);return t<0?a.icon:l[t].icon})),u.bind("label").toMany(l,"isOn",((...e)=>{const t=e.findIndex(Y);return ee(s,t<0?a.label:l[t].label)})),u.bind("isOn").toMany(l,"isOn",((...e)=>e.some(Y))),u.bind("class").toMany(l,"isOn",((...e)=>e.some(Y)?"ck-splitbutton_flatten":null)),u.on("execute",(()=>{l.some((({isOn:e})=>e))?d.isOpen=!d.isOpen:a.fire("execute")})),d.bind("isEnabled").toMany(l,"isEnabled",((...e)=>e.some(Y))),d}))}_createButton(e,t,i){const n=e.name;this.editor.ui.componentFactory.add(te(n,t),(a=>{const r=this.editor.commands.get("drupalElementStyle"),o=new g.ButtonView(a);return o.set({label:e.title,icon:e.icon,tooltip:!0,isToggleable:!0}),o.bind("isEnabled").to(r,"isEnabled"),o.bind("isOn").to(r,"value",(e=>e&&e[t]===n)),o.on("execute",this._executeCommand.bind(this,n,t)),this.listenTo(this.editor.ui,"update",(()=>{this.updateOptionVisibility(i,e,o,t)})),o}))}getDropdownListItemDefinitions(e,t,i){const n=new E.Collection;return e.forEach((t=>{const a={type:"button",model:new g.Model({group:i,commandValue:t.name,label:t.title,withText:!0,class:""})};n.add(a),this.listenTo(this.editor.ui,"update",(()=>{this.updateOptionVisibility(e,t,a,i)}))})),n}_createListDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(n=>{let a;const{defaultItem:r,items:o,title:s,defaultText:l}=e,d=e.name.split(":")[1],u=o.filter((e=>t.find((({name:t})=>te(t,d)===e)))).map((e=>{const t=i.create(e);return e===r&&(a=t),t}));o.length!==u.length&&z.warnInvalidStyle({dropdown:e});const c=(0,g.createDropdown)(n,g.DropdownButtonView),m=c.buttonView;m.set({label:ee(s,a.label),class:null,tooltip:l,withText:!0});const p=this.editor.commands.get("drupalElementStyle");return m.bind("label").to(p,"value",(e=>{if(e&&e[d])for(const i of t)if(i.name===e[d])return i.title;return l})),c.bind("isOn").to(p),c.bind("isEnabled").to(this),(0,g.addListToDropdown)(c,this.getDropdownListItemDefinitions(t,p,d)),this.listenTo(c,"execute",(e=>{this._executeCommand(e.source.commandValue,e.source.group)})),c}))}_executeCommand(e,t){this.editor.execute("drupalElementStyle",{value:e,group:t}),this.editor.editing.view.focus()}static get pluginName(){return"DrupalElementStyleUi"}}class ne extends e.Plugin{static get requires(){return[Q,ie]}static get pluginName(){return"DrupalElementStyle"}}function ae(e){const t=e.getFirstPosition().findAncestor("caption");return t&&a(t.parent)?t:null}function re(e){for(const t of e.getChildren())if(t&&t.is("element","caption"))return t;return null}class oe extends e.Command{refresh(){const e=this.editor.model.document.selection,t=e.getSelectedElement();if(!t)return this.isEnabled=!!o(e),void(this.value=!!ae(e));this.isEnabled=a(t),this.isEnabled?this.value=!!re(t):this.value=!1}execute(e={}){const{focusCaptionOnShow:t}=e;this.editor.model.change((e=>{this.value?this._hideDrupalMediaCaption(e):this._showDrupalMediaCaption(e,t)}))}_showDrupalMediaCaption(e,t){const i=this.editor.model.document.selection,n=this.editor.plugins.get("DrupalMediaCaptionEditing"),a=o(i),r=n._getSavedCaption(a)||e.createElement("caption");e.append(r,a),t&&e.setSelection(r,"in")}_hideDrupalMediaCaption(e){const t=this.editor,i=t.model.document.selection,n=t.plugins.get("DrupalMediaCaptionEditing");let a,r=i.getSelectedElement();r?a=re(r):(a=ae(i),r=o(i)),n._saveCaption(r,a),e.setSelection(r,"on"),e.remove(a)}}class se extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionEditing"}constructor(e){super(e),this._savedCaptionsMap=new WeakMap}init(){const e=this.editor,t=e.model.schema;t.isRegistered("caption")?t.extend("caption",{allowIn:"drupalMedia"}):t.register("caption",{allowIn:"drupalMedia",allowContentOf:"$block",isLimit:!0}),e.commands.add("toggleMediaCaption",new oe(e)),this._setupConversion()}_setupConversion(){const e=this.editor,i=e.editing.view;var n;e.conversion.for("upcast").add(function(e){const t=(t,i,n)=>{const{viewItem:a}=i,{writer:r,consumable:o}=n;if(!i.modelRange||!o.consume(a,{attributes:["data-caption"]}))return;const s=r.createElement("caption"),l=i.modelRange.start.nodeAfter,d=e.data.processor.toView(a.getAttribute("data-caption"));n.consumable.constructor.createFrom(d,n.consumable),n.convertChildren(d,s),r.append(s,l)};return e=>{e.on("element:drupal-media",t,{priority:"low"})}}(e)),e.conversion.for("editingDowncast").elementToElement({model:"caption",view:(e,{writer:n})=>{if(!a(e.parent))return null;const r=n.createEditableElement("figcaption");return r.placeholder=Drupal.t("Enter media caption"),(0,V.enablePlaceholder)({view:i,element:r,keepOnFocus:!0}),(0,t.toWidgetEditable)(r,n)}}),e.editing.mapper.on("modelToViewPosition",(n=i,(e,t)=>{const i=t.modelPosition,r=i.parent;if(!a(r))return;const o=t.mapper.toViewElement(r);t.viewPosition=n.createPositionAt(o,i.offset+1)})),e.conversion.for("dataDowncast").add(function(e){return t=>{t.on("insert:caption",((t,i,n)=>{const{consumable:r,writer:o,mapper:s}=n;if(!a(i.item.parent)||!r.consume(i.item,"insert"))return;const l=e.model.createRangeIn(i.item),d=o.createDocumentFragment();s.bindElements(i.item,d);for(const{item:t}of Array.from(l)){const i={item:t,range:e.model.createRangeOn(t)},a=`insert:${t.name||"$text"}`;e.data.downcastDispatcher.fire(a,i,n);for(const a of t.getAttributeKeys())Object.assign(i,{attributeKey:a,attributeOldValue:null,attributeNewValue:i.item.getAttribute(a)}),e.data.downcastDispatcher.fire(`attribute:${a}`,i,n)}for(const e of o.createRangeIn(d).getItems())s.unbindViewElement(e);s.unbindViewElement(d);const u=e.data.processor.toData(d);if(u){const e=s.toViewElement(i.item.parent);o.setAttribute("data-caption",u,e)}}))}}(e))}_getSavedCaption(e){const t=this._savedCaptionsMap.get(e);return t?V.Element.fromJSON(t):null}_saveCaption(e,t){this._savedCaptionsMap.set(e,t.toJSON())}}class le extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionUI"}init(){const{editor:t}=this,i=t.editing.view;t.ui.componentFactory.add("toggleDrupalMediaCaption",(n=>{const a=new g.ButtonView(n),r=t.commands.get("toggleMediaCaption");return a.set({label:Drupal.t("Caption media"),icon:e.icons.caption,tooltip:!0,isToggleable:!0}),a.bind("isOn","isEnabled").to(r,"value","isEnabled"),a.bind("label").to(r,"value",(e=>e?Drupal.t("Toggle caption off"):Drupal.t("Toggle caption on"))),this.listenTo(a,"execute",(()=>{t.execute("toggleMediaCaption",{focusCaptionOnShow:!0});const e=ae(t.model.document.selection);if(e){const n=t.editing.mapper.toViewElement(e);i.scrollToTheSelection(),i.change((e=>{e.addClass("drupal-media__caption_highlighted",n)}))}t.editing.view.focus()})),a}))}}class de extends e.Plugin{static get requires(){return[se,le]}static get pluginName(){return"DrupalMediaCaption"}}const ue={DrupalMedia:x,MediaImageTextAlternative:A,MediaImageTextAlternativeEditing:y,MediaImageTextAlternativeUi:k,DrupalLinkMedia:B,DrupalMediaCaption:de,DrupalElementStyle:ne}})(),n=n.default})()));
\ No newline at end of file
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.CKEditor5=t():(e.CKEditor5=e.CKEditor5||{},e.CKEditor5.drupalMedia=t())}(globalThis,(()=>(()=>{var e={"ckeditor5/src/core.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/engine.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/engine.js")},"ckeditor5/src/ui.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/ui.js")},"ckeditor5/src/utils.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/utils.js")},"ckeditor5/src/widget.js":(e,t,i)=>{e.exports=i("dll-reference CKEditor5.dll")("./src/widget.js")},"dll-reference CKEditor5.dll":e=>{"use strict";e.exports=CKEditor5.dll}},t={};function i(n){var a=t[n];if(void 0!==a)return a.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,i),r.exports}i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);var n={};return(()=>{"use strict";i.d(n,{default:()=>ue});var e=i("ckeditor5/src/core.js"),t=i("ckeditor5/src/widget.js");function a(e){return!!e&&e.is("element","drupalMedia")}function r(e){return(0,t.isWidget)(e)&&!!e.getCustomProperty("drupalMedia")}function o(e){const t=e.getSelectedElement();return a(t)?t:e.getFirstPosition().findAncestor("drupalMedia")}function s(e){const t=e.getSelectedElement();if(t&&r(t))return t;if(null===e.getFirstPosition())return null;let i=e.getFirstPosition().parent;for(;i;){if(i.is("element")&&r(i))return i;i=i.parent}return null}function l(e){const t=typeof e;return null!=e&&("object"===t||"function"===t)}function d(e){for(const t of e){if(t.hasAttribute("data-drupal-media-preview"))return t;if(t.childCount){const e=d(t.getChildren());if(e)return e}}return null}function u(e){return`drupalElementStyle${e[0].toUpperCase()+e.substring(1)}`}class c extends e.Command{execute(e){const t=this.editor.plugins.get("DrupalMediaEditing"),i=Object.entries(t.attrs).reduce(((e,[t,i])=>(e[i]=t,e)),{}),n=Object.keys(e).reduce(((t,n)=>(i[n]&&(t[i[n]]=e[n]),t)),{});if(this.editor.plugins.has("DrupalElementStyleEditing")){const t=this.editor.plugins.get("DrupalElementStyleEditing"),{normalizedStyles:i}=t;for(const a of Object.keys(i))for(const i of t.normalizedStyles[a])if(e[i.attributeName]&&i.attributeValue===e[i.attributeName]){const e=u(a);n[e]=i.name}}this.editor.model.change((e=>{this.editor.model.insertObject(function(e,t){return e.createElement("drupalMedia",t)}(e,n))}))}refresh(){const e=this.editor.model,t=e.document.selection,i=e.schema.findAllowedParent(t.getFirstPosition(),"drupalMedia");this.isEnabled=null!==i}}const m="METADATA_ERROR";class p extends e.Plugin{static get requires(){return[t.Widget]}constructor(e){super(e),this.attrs={drupalMediaAlt:"alt",drupalMediaEntityType:"data-entity-type",drupalMediaEntityUuid:"data-entity-uuid"},this.converterAttributes=["drupalMediaEntityUuid","drupalElementStyleViewMode","drupalMediaEntityType","drupalMediaAlt"]}init(){const e=this.editor.config.get("drupalMedia");if(!e)return;const{previewURL:t,themeError:i}=e;this.previewUrl=t,this.labelError=Drupal.t("Preview failed"),this.themeError=i||`\n      <p>${Drupal.t("An error occurred while trying to preview the media. Save your work and reload this page.")}<p>\n    `,this._defineSchema(),this._defineConverters(),this._defineListeners(),this.editor.commands.add("insertDrupalMedia",new c(this.editor))}upcastDrupalMediaIsImage(e){const{model:t,plugins:i}=this.editor;i.get("DrupalMediaMetadataRepository").getMetadata(e).then((i=>{e&&t.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaIsImage",!!i.imageSourceMetadata,e)}))})).catch((i=>{e&&(console.warn(i.toString()),t.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaIsImage",m,e)})))}))}upcastDrupalMediaType(e){this.editor.plugins.get("DrupalMediaMetadataRepository").getMetadata(e).then((t=>{e&&this.editor.model.enqueueChange({isUndoable:!1},(i=>{i.setAttribute("drupalMediaType",t.type,e)}))})).catch((t=>{e&&(console.warn(t.toString()),this.editor.model.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaType",m,e)})))}))}async _fetchPreview(e){const t={text:this._renderElement(e),uuid:e.getAttribute("drupalMediaEntityUuid")},i=await fetch(`${this.previewUrl}?${new URLSearchParams(t)}`,{headers:{"X-Drupal-MediaPreview-CSRF-Token":this.editor.config.get("drupalMedia").previewCsrfToken}});if(i.ok){return{label:i.headers.get("drupal-media-label"),preview:await i.text()}}return{label:this.labelError,preview:this.themeError}}_defineSchema(){this.editor.model.schema.register("drupalMedia",{inheritAllFrom:"$blockObject",allowAttributes:Object.keys(this.attrs)}),this.editor.editing.view.domConverter.blockElements.push("drupal-media")}_defineConverters(){const e=this.editor.conversion,i=this.editor.plugins.get("DrupalMediaMetadataRepository");e.for("upcast").elementToElement({view:{name:"drupal-media"},model:"drupalMedia"}).add((e=>{e.on("element:drupal-media",((e,t)=>{const[n]=t.modelRange.getItems();i.getMetadata(n).then((e=>{n&&(this.upcastDrupalMediaIsImage(n),this.editor.model.enqueueChange({isUndoable:!1},(t=>{t.setAttribute("drupalMediaType",e.type,n)})))})).catch((e=>{console.warn(e.toString())}))}),{priority:"lowest"})})),e.for("dataDowncast").elementToElement({model:"drupalMedia",view:{name:"drupal-media"}}),e.for("editingDowncast").elementToElement({model:"drupalMedia",view:(e,{writer:i})=>{const n=i.createContainerElement("figure",{class:"drupal-media"});if(!this.previewUrl){const e=i.createRawElement("div",{"data-drupal-media-preview":"unavailable"});i.insert(i.createPositionAt(n,0),e)}return i.setCustomProperty("drupalMedia",!0,n),(0,t.toWidget)(n,i,{label:Drupal.t("Media widget")})}}).add((e=>{const t=(e,t,i)=>{const n=i.writer,a=t.item,r=i.mapper.toViewElement(t.item);let o=d(r.getChildren());if(o){if("ready"!==o.getAttribute("data-drupal-media-preview"))return;n.setAttribute("data-drupal-media-preview","loading",o)}else o=n.createRawElement("div",{"data-drupal-media-preview":"loading"}),n.insert(n.createPositionAt(r,0),o);this._fetchPreview(a).then((({label:e,preview:t})=>{o&&this.editor.editing.view.change((i=>{const n=i.createRawElement("div",{"data-drupal-media-preview":"ready","aria-label":e},(e=>{e.innerHTML=t}));i.insert(i.createPositionBefore(o),n),i.remove(o)}))}))};return this.converterAttributes.forEach((i=>{e.on(`attribute:${i}:drupalMedia`,t)})),e})),e.for("editingDowncast").add((e=>{e.on("attribute:drupalElementStyleAlign:drupalMedia",((e,t,i)=>{const n={left:"drupal-media-style-align-left",right:"drupal-media-style-align-right",center:"drupal-media-style-align-center"},a=i.mapper.toViewElement(t.item),r=i.writer;n[t.attributeOldValue]&&r.removeClass(n[t.attributeOldValue],a),n[t.attributeNewValue]&&i.consumable.consume(t.item,e.name)&&r.addClass(n[t.attributeNewValue],a)}))})),Object.keys(this.attrs).forEach((t=>{const i={model:{key:t,name:"drupalMedia"},view:{name:"drupal-media",key:this.attrs[t]}};e.for("dataDowncast").attributeToAttribute(i),e.for("upcast").attributeToAttribute(i)}))}_defineListeners(){this.editor.model.on("insertContent",((e,[t])=>{a(t)&&(this.upcastDrupalMediaIsImage(t),this.upcastDrupalMediaType(t))}))}_renderElement(e){const t=this.editor.model.change((t=>{const i=t.createDocumentFragment(),n=t.cloneElement(e,!1);return["linkHref"].forEach((e=>{t.removeAttribute(e,n)})),t.append(n,i),i}));return this.editor.data.stringify(t)}static get pluginName(){return"DrupalMediaEditing"}}var g=i("ckeditor5/src/ui.js");class h extends e.Plugin{init(){const e=this.editor,t=this.editor.config.get("drupalMedia");if(!t)return;const{libraryURL:i,openDialog:n,dialogSettings:a={}}=t;i&&"function"==typeof n&&e.ui.componentFactory.add("drupalMedia",(t=>{const r=e.commands.get("insertDrupalMedia"),o=new g.ButtonView(t);return o.set({label:Drupal.t("Insert Media"),icon:'<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.1873 4.86414L10.2509 6.86414V7.02335H10.2499V15.5091C9.70972 15.1961 9.01793 15.1048 8.34069 15.3136C7.12086 15.6896 6.41013 16.8967 6.75322 18.0096C7.09631 19.1226 8.3633 19.72 9.58313 19.344C10.6666 19.01 11.3484 18.0203 11.2469 17.0234H11.2499V9.80173L18.1803 8.25067V14.3868C17.6401 14.0739 16.9483 13.9825 16.2711 14.1913C15.0513 14.5674 14.3406 15.7744 14.6836 16.8875C15.0267 18.0004 16.2937 18.5978 17.5136 18.2218C18.597 17.8877 19.2788 16.8982 19.1773 15.9011H19.1803V8.02687L19.1873 8.0253V4.86414Z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13.5039 0.743652H0.386932V12.1603H13.5039V0.743652ZM12.3379 1.75842H1.55289V11.1454H1.65715L4.00622 8.86353L6.06254 10.861L9.24985 5.91309L11.3812 9.22179L11.7761 8.6676L12.3379 9.45621V1.75842ZM6.22048 4.50869C6.22048 5.58193 5.35045 6.45196 4.27722 6.45196C3.20398 6.45196 2.33395 5.58193 2.33395 4.50869C2.33395 3.43546 3.20398 2.56543 4.27722 2.56543C5.35045 2.56543 6.22048 3.43546 6.22048 4.50869Z"/></svg>\n',tooltip:!0}),o.bind("isOn","isEnabled").to(r,"value","isEnabled"),this.listenTo(o,"execute",(()=>{n(i,(({attributes:t})=>{e.execute("insertDrupalMedia",t)}),a)})),o}))}}class f extends e.Plugin{static get requires(){return[t.WidgetToolbarRepository]}static get pluginName(){return"DrupalMediaToolbar"}afterInit(){const{editor:e}=this;var i;e.plugins.get(t.WidgetToolbarRepository).register("drupalMedia",{ariaLabel:Drupal.t("Drupal Media toolbar"),items:(i=e.config.get("drupalMedia.toolbar"),i.map((e=>l(e)?e.name:e))||[]),getRelatedElement:e=>s(e)})}}class b extends e.Command{refresh(){const e=o(this.editor.model.document.selection);this.isEnabled=!!e&&e.getAttribute("drupalMediaIsImage")&&e.getAttribute("drupalMediaIsImage")!==m,this.isEnabled?this.value=e.getAttribute("drupalMediaAlt"):this.value=!1}execute(e){const{model:t}=this.editor,i=o(t.document.selection);e.newValue=e.newValue.trim(),t.change((t=>{e.newValue.length>0?t.setAttribute("drupalMediaAlt",e.newValue,i):t.removeAttribute("drupalMediaAlt",i)}))}}class w extends e.Plugin{init(){this._data=new WeakMap}getMetadata(e){if(this._data.get(e))return new Promise((t=>{t(this._data.get(e))}));const t=this.editor.config.get("drupalMedia");if(!t)return new Promise(((e,t)=>{t(new Error("drupalMedia configuration is required for parsing metadata."))}));if(!e.hasAttribute("drupalMediaEntityUuid"))return new Promise(((e,t)=>{t(new Error("drupalMedia element must have drupalMediaEntityUuid attribute to retrieve metadata."))}));const{metadataUrl:i}=t;return(async e=>{const t=await fetch(e);if(t.ok)return JSON.parse(await t.text());throw new Error("Fetching media embed metadata from the server failed.")})(`${i}&${new URLSearchParams({uuid:e.getAttribute("drupalMediaEntityUuid")})}`).then((t=>(this._data.set(e,t),t)))}static get pluginName(){return"DrupalMediaMetadataRepository"}}class y extends e.Plugin{static get requires(){return[w]}static get pluginName(){return"MediaImageTextAlternativeEditing"}init(){const{editor:e,editor:{model:t,conversion:i}}=this;t.schema.extend("drupalMedia",{allowAttributes:["drupalMediaIsImage"]}),i.for("editingDowncast").add((e=>{e.on("attribute:drupalMediaIsImage",((e,t,i)=>{const{writer:n,mapper:a}=i,r=a.toViewElement(t.item);if(t.attributeNewValue!==m){const e=Array.from(r.getChildren()).find((e=>e.getCustomProperty("drupalMediaMetadataError")));return void(e&&(n.setCustomProperty("widgetLabel",e.getCustomProperty("drupalMediaOriginalWidgetLabel"),e),n.removeElement(e)))}const o=Drupal.t("Not all functionality may be available because some information could not be retrieved."),s=new g.Template({tag:"span",children:[{tag:"span",attributes:{class:"drupal-media__metadata-error-icon","data-cke-tooltip-text":o}}]}).render(),l=n.createRawElement("div",{class:"drupal-media__metadata-error"},((e,t)=>{t.setContentOf(e,s.outerHTML)}));n.setCustomProperty("drupalMediaMetadataError",!0,l);const d=r.getCustomProperty("widgetLabel");n.setCustomProperty("drupalMediaOriginalWidgetLabel",d,l),n.setCustomProperty("widgetLabel",`${d} (${o})`,r),n.insert(n.createPositionAt(r,0),l)}),{priority:"low"})})),e.commands.add("mediaImageTextAlternative",new b(this.editor))}}function v(e){const t=e.editing.view,i=g.BalloonPanelView.defaultPositions;return{target:t.domConverter.viewToDom(t.document.selection.getSelectedElement()),positions:[i.northArrowSouth,i.northArrowSouthWest,i.northArrowSouthEast,i.southArrowNorth,i.southArrowNorthWest,i.southArrowNorthEast]}}var E=i("ckeditor5/src/utils.js");class M extends g.View{constructor(t){super(t),this.focusTracker=new E.FocusTracker,this.keystrokes=new E.KeystrokeHandler,this.labeledInput=this._createLabeledInputView(),this.set("defaultAltText",void 0),this.defaultAltTextView=this._createDefaultAltTextView(),this.saveButtonView=this._createButton(Drupal.t("Save"),e.icons.check,"ck-button-save"),this.saveButtonView.type="submit",this.cancelButtonView=this._createButton(Drupal.t("Cancel"),e.icons.cancel,"ck-button-cancel","cancel"),this._focusables=new g.ViewCollection,this._focusCycler=new g.FocusCycler({focusables:this._focusables,focusTracker:this.focusTracker,keystrokeHandler:this.keystrokes,actions:{focusPrevious:"shift + tab",focusNext:"tab"}}),this.setTemplate({tag:"form",attributes:{class:["ck","ck-media-alternative-text-form","ck-vertical-form"],tabindex:"-1"},children:[this.defaultAltTextView,this.labeledInput,this.saveButtonView,this.cancelButtonView]}),(0,g.injectCssTransitionDisabler)(this)}render(){super.render(),this.keystrokes.listenTo(this.element),(0,g.submitHandler)({view:this}),[this.labeledInput,this.saveButtonView,this.cancelButtonView].forEach((e=>{this._focusables.add(e),this.focusTracker.add(e.element)}))}_createButton(e,t,i,n){const a=new g.ButtonView(this.locale);return a.set({label:e,icon:t,tooltip:!0}),a.extendTemplate({attributes:{class:i}}),n&&a.delegate("execute").to(this,n),a}_createLabeledInputView(){const e=new g.LabeledFieldView(this.locale,g.createLabeledInputText);return e.label=Drupal.t("Alternative text override"),e}_createDefaultAltTextView(){const e=g.Template.bind(this,this);return new g.Template({tag:"div",attributes:{class:["ck-media-alternative-text-form__default-alt-text",e.if("defaultAltText","ck-hidden",(e=>!e))]},children:[{tag:"strong",attributes:{class:"ck-media-alternative-text-form__default-alt-text-label"},children:[Drupal.t("Default alternative text:")]}," ",{tag:"span",attributes:{class:"ck-media-alternative-text-form__default-alt-text-value"},children:[{text:[e.to("defaultAltText")]}]}]})}}class k extends e.Plugin{static get requires(){return[g.ContextualBalloon]}static get pluginName(){return"MediaImageTextAlternativeUi"}init(){this._createButton(),this._createForm()}destroy(){super.destroy(),this._form.destroy()}_createButton(){const t=this.editor;t.ui.componentFactory.add("mediaImageTextAlternative",(i=>{const n=t.commands.get("mediaImageTextAlternative"),a=new g.ButtonView(i);return a.set({label:Drupal.t("Override media image alternative text"),icon:e.icons.lowVision,tooltip:!0}),a.bind("isVisible").to(n,"isEnabled"),this.listenTo(a,"execute",(()=>{this._showForm()})),a}))}_createForm(){const e=this.editor,t=e.editing.view.document;this._balloon=this.editor.plugins.get("ContextualBalloon"),this._form=new M(e.locale),this._form.render(),this.listenTo(this._form,"submit",(()=>{e.execute("mediaImageTextAlternative",{newValue:this._form.labeledInput.fieldView.element.value}),this._hideForm(!0)})),this.listenTo(this._form,"cancel",(()=>{this._hideForm(!0)})),this._form.keystrokes.set("Esc",((e,t)=>{this._hideForm(!0),t()})),this.listenTo(e.ui,"update",(()=>{s(t.selection)?this._isVisible&&function(e){const t=e.plugins.get("ContextualBalloon");if(s(e.editing.view.document.selection)){const i=v(e);t.updatePosition(i)}}(e):this._hideForm(!0)})),(0,g.clickOutsideHandler)({emitter:this._form,activator:()=>this._isVisible,contextElements:[this._balloon.view.element],callback:()=>this._hideForm()})}_showForm(){if(this._isVisible)return;const e=this.editor,t=e.commands.get("mediaImageTextAlternative"),i=e.plugins.get("DrupalMediaMetadataRepository"),n=this._form.labeledInput;this._form.disableCssTransitions(),this._isInBalloon||this._balloon.add({view:this._form,position:v(e)}),n.fieldView.element.value=t.value||"",n.fieldView.value=n.fieldView.element.value,this._form.defaultAltText="";const r=e.model.document.selection.getSelectedElement();a(r)&&i.getMetadata(r).then((e=>{this._form.defaultAltText=e.imageSourceMetadata?e.imageSourceMetadata.alt:""})).catch((e=>{console.warn(e.toString())})),this._form.labeledInput.fieldView.select(),this._form.enableCssTransitions()}_hideForm(e){this._isInBalloon&&(this._form.focusTracker.isFocused&&this._form.saveButtonView.focus(),this._balloon.remove(this._form),e&&this.editor.editing.view.focus())}get _isVisible(){return this._balloon.visibleView===this._form}get _isInBalloon(){return this._balloon.hasView(this._form)}}class A extends e.Plugin{static get requires(){return[y,k]}static get pluginName(){return"MediaImageTextAlternative"}}function D(e,t,i){if(t.attributes)for(const[n,a]of Object.entries(t.attributes))e.setAttribute(n,a,i);t.styles&&e.setStyle(t.styles,i),t.classes&&e.addClass(t.classes,i)}function C(e,t,i){if(!i.consumable.consume(t.item,e.name))return;const n=i.mapper.toViewElement(t.item);D(i.writer,t.attributeNewValue,n)}class _ extends e.Plugin{constructor(e){if(super(e),!e.plugins.has("GeneralHtmlSupport"))return;e.plugins.has("DataFilter")&&e.plugins.has("DataSchema")||console.error("DataFilter and DataSchema plugins are required for Drupal Media to integrate with General HTML Support plugin.");const{schema:t}=e.model,{conversion:i}=e,n=this.editor.plugins.get("DataFilter");this.editor.plugins.get("DataSchema").registerBlockElement({model:"drupalMedia",view:"drupal-media"}),n.on("register:drupal-media",((e,a)=>{"drupalMedia"===a.model&&(t.extend("drupalMedia",{allowAttributes:["htmlLinkAttributes","htmlAttributes"]}),i.for("upcast").add(function(e){return t=>{t.on("element:drupal-media",((t,i,n)=>{function a(t,a){const r=e.processViewAttributes(t,n);r&&n.writer.setAttribute(a,r,i.modelRange)}const r=i.viewItem,o=r.parent;a(r,"htmlAttributes"),o.is("element","a")&&a(o,"htmlLinkAttributes")}),{priority:"low"})}}(n)),i.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const n=i.mapper.toViewElement(t.item),a=function(e,t,i){const n=e.createRangeOn(t);for(const{item:e}of n.getWalker())if(e.is("element",i))return e}(i.writer,n,"a");D(i.writer,t.item.getAttribute("htmlLinkAttributes"),a)}),{priority:"low"})})),i.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{if(!i.consumable.consume(t.item,"attribute:htmlLinkAttributes:drupalMedia"))return;const n=i.mapper.toViewElement(t.item).parent;D(i.writer,t.item.getAttribute("htmlLinkAttributes"),n)}),{priority:"low"}),e.on("attribute:htmlAttributes:drupalMedia",C,{priority:"low"})})),e.stop())}))}static get pluginName(){return"DrupalMediaGeneralHtmlSupport"}}class x extends e.Plugin{static get requires(){return[p,_,h,f,A]}static get pluginName(){return"DrupalMedia"}}var V=i("ckeditor5/src/engine.js");function S(e){return Array.from(e.getChildren()).find((e=>"drupal-media"===e.name))}function T(e){return t=>{t.on(`attribute:${e.id}:drupalMedia`,((t,i,n)=>{const a=n.mapper.toViewElement(i.item);let r=Array.from(a.getChildren()).find((e=>"a"===e.name));if(r=!r&&a.is("element","a")?a:Array.from(a.getAncestors()).find((e=>"a"===e.name)),r){for(const[t,i]of(0,E.toMap)(e.attributes))n.writer.setAttribute(t,i,r);e.classes&&n.writer.addClass(e.classes,r);for(const t in e.styles)Object.prototype.hasOwnProperty.call(e.styles,t)&&n.writer.setStyle(t,e.styles[t],r)}}))}}function I(e,t){return e=>{e.on("element:a",((e,i,n)=>{const a=i.viewItem;if(!S(a))return;const r=new V.Matcher(t._createPattern()).match(a);if(!r)return;if(!n.consumable.consume(a,r.match))return;const o=i.modelCursor.nodeBefore;n.writer.setAttribute(t.id,!0,o)}),{priority:"high"})}}class L extends e.Plugin{static get requires(){return["LinkEditing","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaEditing"}init(){const{editor:e}=this;e.model.schema.extend("drupalMedia",{allowAttributes:["linkHref"]}),e.conversion.for("upcast").add((e=>{e.on("element:a",((e,t,i)=>{const n=t.viewItem,a=S(n);if(!a)return;if(!i.consumable.consume(n,{attributes:["href"],name:!0}))return;const r=n.getAttribute("href");if(!r)return;const o=i.convertItem(a,t.modelCursor);t.modelRange=o.modelRange,t.modelCursor=o.modelCursor;const s=t.modelCursor.nodeBefore;s&&s.is("element","drupalMedia")&&i.writer.setAttribute("linkHref",r,s)}),{priority:"high"})})),e.conversion.for("editingDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:n}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),r=Array.from(a.getChildren()).find((e=>"a"===e.name));if(r)t.attributeNewValue?n.setAttribute("href",t.attributeNewValue,r):(n.move(n.createRangeIn(r),n.createPositionAt(a,0)),n.remove(r));else{const e=Array.from(a.getChildren()).find((e=>e.getAttribute("data-drupal-media-preview"))),i=n.createContainerElement("a",{href:t.attributeNewValue});n.insert(n.createPositionAt(a,0),i),n.move(n.createRangeOn(e),n.createPositionAt(i,0))}}),{priority:"high"})})),e.conversion.for("dataDowncast").add((e=>{e.on("attribute:linkHref:drupalMedia",((e,t,i)=>{const{writer:n}=i;if(!i.consumable.consume(t.item,e.name))return;const a=i.mapper.toViewElement(t.item),r=n.createContainerElement("a",{href:t.attributeNewValue});n.insert(n.createPositionBefore(a),r),n.move(n.createRangeOn(a),n.createPositionAt(r,0))}),{priority:"high"})})),this._enableManualDecorators();if(e.commands.get("link").automaticDecorators.length>0)throw new Error("The Drupal Media plugin is not compatible with automatic link decorators. To use Drupal Media, disable any plugins providing automatic link decorators.")}_enableManualDecorators(){const e=this.editor,t=e.commands.get("link");for(const i of t.manualDecorators)e.model.schema.extend("drupalMedia",{allowAttributes:i.id}),e.conversion.for("downcast").add(T(i)),e.conversion.for("upcast").add(I(0,i))}}class P extends e.Plugin{static get requires(){return["LinkEditing","LinkUI","DrupalMediaEditing"]}static get pluginName(){return"DrupalLinkMediaUi"}init(){const{editor:e}=this,t=e.editing.view.document;this.listenTo(t,"click",((t,i)=>{this._isSelectedLinkedMedia(e.model.document.selection)&&(i.preventDefault(),t.stop())}),{priority:"high"}),this._createToolbarLinkMediaButton()}_createToolbarLinkMediaButton(){const{editor:e}=this;e.ui.componentFactory.add("drupalLinkMedia",(t=>{const i=new g.ButtonView(t),n=e.plugins.get("LinkUI"),a=e.commands.get("link");return i.set({isEnabled:!0,label:Drupal.t("Link media"),icon:'<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="m11.077 15 .991-1.416a.75.75 0 1 1 1.229.86l-1.148 1.64a.748.748 0 0 1-.217.206 5.251 5.251 0 0 1-8.503-5.955.741.741 0 0 1 .12-.274l1.147-1.639a.75.75 0 1 1 1.228.86L4.933 10.7l.006.003a3.75 3.75 0 0 0 6.132 4.294l.006.004zm5.494-5.335a.748.748 0 0 1-.12.274l-1.147 1.639a.75.75 0 1 1-1.228-.86l.86-1.23a3.75 3.75 0 0 0-6.144-4.301l-.86 1.229a.75.75 0 0 1-1.229-.86l1.148-1.64a.748.748 0 0 1 .217-.206 5.251 5.251 0 0 1 8.503 5.955zm-4.563-2.532a.75.75 0 0 1 .184 1.045l-3.155 4.505a.75.75 0 1 1-1.229-.86l3.155-4.506a.75.75 0 0 1 1.045-.184z"/></svg>\n',keystroke:"Ctrl+K",tooltip:!0,isToggleable:!0}),i.bind("isEnabled").to(a,"isEnabled"),i.bind("isOn").to(a,"value",(e=>!!e)),this.listenTo(i,"execute",(()=>{this._isSelectedLinkedMedia(e.model.document.selection)?n._addActionsView():n._showUI(!0)})),i}))}_isSelectedLinkedMedia(e){const t=e.getSelectedElement();return!!t&&t.is("element","drupalMedia")&&t.hasAttribute("linkHref")}}class O extends e.Plugin{static get requires(){return[L,P]}static get pluginName(){return"DrupalLinkMedia"}}const{objectFullWidth:B,objectInline:N,objectLeft:R,objectRight:j,objectCenter:F,objectBlockLeft:U,objectBlockRight:H}=e.icons,$={get inline(){return{name:"inline",title:"In line",icon:N,modelElements:["imageInline"],isDefault:!0}},get alignLeft(){return{name:"alignLeft",title:"Left aligned image",icon:R,modelElements:["imageBlock","imageInline"],className:"image-style-align-left"}},get alignBlockLeft(){return{name:"alignBlockLeft",title:"Left aligned image",icon:U,modelElements:["imageBlock"],className:"image-style-block-align-left"}},get alignCenter(){return{name:"alignCenter",title:"Centered image",icon:F,modelElements:["imageBlock"],className:"image-style-align-center"}},get alignRight(){return{name:"alignRight",title:"Right aligned image",icon:j,modelElements:["imageBlock","imageInline"],className:"image-style-align-right"}},get alignBlockRight(){return{name:"alignBlockRight",title:"Right aligned image",icon:H,modelElements:["imageBlock"],className:"image-style-block-align-right"}},get block(){return{name:"block",title:"Centered image",icon:F,modelElements:["imageBlock"],isDefault:!0}},get side(){return{name:"side",title:"Side image",icon:j,modelElements:["imageBlock"],className:"image-style-side"}}},q={full:B,left:U,right:H,center:F,inlineLeft:R,inlineRight:j,inline:N},W=[{name:"imageStyle:wrapText",title:"Wrap text",defaultItem:"imageStyle:alignLeft",items:["imageStyle:alignLeft","imageStyle:alignRight"]},{name:"imageStyle:breakText",title:"Break text",defaultItem:"imageStyle:block",items:["imageStyle:alignBlockLeft","imageStyle:block","imageStyle:alignBlockRight"]}];function K(e){(0,E.logWarning)("image-style-configuration-definition-invalid",e)}const z={normalizeStyles:function(e){return(e.configuredStyles.options||[]).map((e=>function(e){e="string"==typeof e?$[e]?{...$[e]}:{name:e}:function(e,t){const i={...t};for(const n in e)Object.prototype.hasOwnProperty.call(t,n)||(i[n]=e[n]);return i}($[e.name],e);"string"==typeof e.icon&&(e.icon=q[e.icon]||e.icon);return e}(e))).filter((t=>function(e,{isBlockPluginLoaded:t,isInlinePluginLoaded:i}){const{modelElements:n,name:a}=e;if(!(n&&n.length&&a))return K({style:e}),!1;{const a=[t?"imageBlock":null,i?"imageInline":null];if(!n.some((e=>a.includes(e))))return(0,E.logWarning)("image-style-missing-dependency",{style:e,missingPlugins:n.map((e=>"imageBlock"===e?"ImageBlockEditing":"ImageInlineEditing"))}),!1}return!0}(t,e)))},getDefaultStylesConfiguration:function(e,t){return e&&t?{options:["inline","alignLeft","alignRight","alignCenter","alignBlockLeft","alignBlockRight","block","side"]}:e?{options:["block","side"]}:t?{options:["inline","alignLeft","alignRight"]}:{}},getDefaultDropdownDefinitions:function(e){return e.has("ImageBlockEditing")&&e.has("ImageInlineEditing")?[...W]:[]},warnInvalidStyle:K,DEFAULT_OPTIONS:$,DEFAULT_ICONS:q,DEFAULT_DROPDOWN_DEFINITIONS:W};function Z(e,t,i){for(const n of t)if(i.checkAttribute(e,n))return!0;return!1}function G(e,t,i){const n=e.getSelectedElement();if(n&&Z(n,i,t))return n;let{parent:a}=e.getFirstPosition();for(;a;){if(a.is("element")&&Z(a,i,t))return a;a=a.parent}return null}class J extends e.Command{constructor(e,t){super(e),this.styles={},Object.keys(t).forEach((e=>{this.styles[e]=new Map(t[e].map((e=>[e.name,e])))})),this.modelAttributes=[];for(const e of Object.keys(t)){const t=u(e);this.modelAttributes.push(t)}}refresh(){const{editor:e}=this,t=G(e.model.document.selection,e.model.schema,this.modelAttributes);this.isEnabled=!!t,this.isEnabled?this.value=this.getValue(t):this.value=!1}getValue(e){const t={};return Object.keys(this.styles).forEach((i=>{const n=u(i);if(e.hasAttribute(n))t[i]=e.getAttribute(n);else for(const[,e]of this.styles[i])e.isDefault&&(t[i]=e.name)})),t}execute(e={}){const{editor:{model:t}}=this,{value:i,group:n}=e,a=u(n);t.change((e=>{const r=G(t.document.selection,t.schema,this.modelAttributes);!i||this.styles[n].get(i).isDefault?e.removeAttribute(a,r):e.setAttribute(a,i,r)}))}}function X(e,t){for(const i of t)if(i.name===e)return i}class Q extends e.Plugin{init(){const{editor:t}=this,i=t.config.get("drupalElementStyles");this.normalizedStyles={},Object.keys(i).forEach((t=>{this.normalizedStyles[t]=i[t].map((t=>("string"==typeof t.icon&&e.icons[t.icon]&&(t.icon=e.icons[t.icon]),t.name&&(t.name=`${t.name}`),t))).filter((e=>e.isDefault||e.attributeName&&e.attributeValue?e.modelElements&&Array.isArray(e.modelElements)?!!e.name||(console.warn("drupalElementStyles options must include a name."),!1):(console.warn("drupalElementStyles options must include an array of supported modelElements."),!1):(console.warn(`${e.attributeValue} drupalElementStyles options must include attributeName and attributeValue.`),!1)))})),this._setupConversion(),t.commands.add("drupalElementStyle",new J(t,this.normalizedStyles))}_setupConversion(){const{editor:e}=this,{schema:t}=e.model;Object.keys(this.normalizedStyles).forEach((i=>{const n=u(i),a=(r=this.normalizedStyles[i],(e,t,i)=>{if(!i.consumable.consume(t.item,e.name))return;const n=X(t.attributeNewValue,r),a=X(t.attributeOldValue,r),o=i.mapper.toViewElement(t.item),s=i.writer;a&&("class"===a.attributeName?s.removeClass(a.attributeValue,o):s.removeAttribute(a.attributeName,o)),n&&("class"===n.attributeName?s.addClass(n.attributeValue,o):n.isDefault||s.setAttribute(n.attributeName,n.attributeValue,o))});var r;const o=function(e,t){const i=e.filter((e=>!e.isDefault));return(e,n,a)=>{if(!n.modelRange)return;const r=n.viewItem,o=(0,E.first)(n.modelRange.getItems());if(o&&a.schema.checkAttribute(o,t))for(const e of i)if("class"===e.attributeName)a.consumable.consume(r,{classes:e.attributeValue})&&a.writer.setAttribute(t,e.name,o);else if(a.consumable.consume(r,{attributes:[e.attributeName]}))for(const e of i)e.attributeValue===r.getAttribute(e.attributeName)&&a.writer.setAttribute(t,e.name,o)}}(this.normalizedStyles[i],n);e.editing.downcastDispatcher.on(`attribute:${n}`,a),e.data.downcastDispatcher.on(`attribute:${n}`,a);[...new Set(this.normalizedStyles[i].map((e=>e.modelElements)).flat())].forEach((e=>{t.extend(e,{allowAttributes:n})})),e.data.upcastDispatcher.on("element",o,{priority:"low"})}))}static get pluginName(){return"DrupalElementStyleEditing"}}const Y=e=>e,ee=(e,t)=>(e?`${e}: `:"")+t;function te(e,t){return`drupalElementStyle:${t}:${e}`}class ie extends e.Plugin{static get requires(){return[Q]}init(){const{plugins:e}=this.editor,t=this.editor.config.get("drupalMedia.toolbar")||[],i=e.get("DrupalElementStyleEditing").normalizedStyles;Object.keys(i).forEach((e=>{i[e].forEach((t=>{this._createButton(t,e,i[e])}))}));t.filter(l).filter((e=>{const t=[];if(!e.display)return console.warn("dropdown configuration must include a display key specifying either listDropdown or splitButton."),!1;e.items.includes(e.defaultItem)||console.warn("defaultItem must be part of items in the dropdown configuration.");for(const i of e.items){const e=i.split(":")[1];t.push(e)}return!!t.every((e=>e===t[0]))||(console.warn("dropdown configuration should only contain buttons from one group."),!1)})).forEach((e=>{if(e.items.length>=2){const t=e.name.split(":")[1];switch(e.display){case"splitButton":this._createDropdown(e,i[t]);break;case"listDropdown":this._createListDropdown(e,i[t])}}}))}updateOptionVisibility(e,t,i,n){const{selection:a}=this.editor.model.document,r={};r[n]=e;const o=a?a.getSelectedElement():G(a,this.editor.model.schema,r),s=e.filter((function(e){for(const[t,i]of(0,E.toMap)(e.modelAttributes))if(o&&o.hasAttribute(t))return i.includes(o.getAttribute(t));return!0}));i.hasOwnProperty("model")?s.includes(t)?i.model.set({class:""}):i.model.set({class:"ck-hidden"}):s.includes(t)?i.set({class:""}):i.set({class:"ck-hidden"})}_createDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(n=>{let a;const{defaultItem:r,items:o,title:s}=e,l=o.filter((e=>{const i=e.split(":")[1];return t.find((({name:t})=>te(t,i)===e))})).map((e=>{const t=i.create(e);return e===r&&(a=t),t}));o.length!==l.length&&z.warnInvalidStyle({dropdown:e});const d=(0,g.createDropdown)(n,g.SplitButtonView),u=d.buttonView;return(0,g.addToolbarToDropdown)(d,l),u.set({label:ee(s,a.label),class:null,tooltip:!0}),u.bind("icon").toMany(l,"isOn",((...e)=>{const t=e.findIndex(Y);return t<0?a.icon:l[t].icon})),u.bind("label").toMany(l,"isOn",((...e)=>{const t=e.findIndex(Y);return ee(s,t<0?a.label:l[t].label)})),u.bind("isOn").toMany(l,"isOn",((...e)=>e.some(Y))),u.bind("class").toMany(l,"isOn",((...e)=>e.some(Y)?"ck-splitbutton_flatten":null)),u.on("execute",(()=>{l.some((({isOn:e})=>e))?d.isOpen=!d.isOpen:a.fire("execute")})),d.bind("isEnabled").toMany(l,"isEnabled",((...e)=>e.some(Y))),d}))}_createButton(e,t,i){const n=e.name;this.editor.ui.componentFactory.add(te(n,t),(a=>{const r=this.editor.commands.get("drupalElementStyle"),o=new g.ButtonView(a);return o.set({label:e.title,icon:e.icon,tooltip:!0,isToggleable:!0}),o.bind("isEnabled").to(r,"isEnabled"),o.bind("isOn").to(r,"value",(e=>e&&e[t]===n)),o.on("execute",this._executeCommand.bind(this,n,t)),this.listenTo(this.editor.ui,"update",(()=>{this.updateOptionVisibility(i,e,o,t)})),o}))}getDropdownListItemDefinitions(e,t,i){const n=new E.Collection;return e.forEach((t=>{const a={type:"button",model:new g.Model({group:i,commandValue:t.name,label:t.title,withText:!0,class:""})};n.add(a),this.listenTo(this.editor.ui,"update",(()=>{this.updateOptionVisibility(e,t,a,i)}))})),n}_createListDropdown(e,t){const i=this.editor.ui.componentFactory;i.add(e.name,(n=>{let a;const{defaultItem:r,items:o,title:s,defaultText:l}=e,d=e.name.split(":")[1],u=o.filter((e=>t.find((({name:t})=>te(t,d)===e)))).map((e=>{const t=i.create(e);return e===r&&(a=t),t}));o.length!==u.length&&z.warnInvalidStyle({dropdown:e});const c=(0,g.createDropdown)(n,g.DropdownButtonView),m=c.buttonView;m.set({label:ee(s,a.label),class:null,tooltip:l,withText:!0});const p=this.editor.commands.get("drupalElementStyle");return m.bind("label").to(p,"value",(e=>{if(e&&e[d])for(const i of t)if(i.name===e[d])return i.title;return l})),c.bind("isOn").to(p),c.bind("isEnabled").to(this),(0,g.addListToDropdown)(c,this.getDropdownListItemDefinitions(t,p,d)),this.listenTo(c,"execute",(e=>{this._executeCommand(e.source.commandValue,e.source.group)})),c}))}_executeCommand(e,t){this.editor.execute("drupalElementStyle",{value:e,group:t}),this.editor.editing.view.focus()}static get pluginName(){return"DrupalElementStyleUi"}}class ne extends e.Plugin{static get requires(){return[Q,ie]}static get pluginName(){return"DrupalElementStyle"}}function ae(e){const t=e.getFirstPosition().findAncestor("caption");return t&&a(t.parent)?t:null}function re(e){for(const t of e.getChildren())if(t&&t.is("element","caption"))return t;return null}class oe extends e.Command{refresh(){const e=this.editor.model.document.selection,t=e.getSelectedElement();if(!t)return this.isEnabled=!!o(e),void(this.value=!!ae(e));this.isEnabled=a(t),this.isEnabled?this.value=!!re(t):this.value=!1}execute(e={}){const{focusCaptionOnShow:t}=e;this.editor.model.change((e=>{this.value?this._hideDrupalMediaCaption(e):this._showDrupalMediaCaption(e,t)}))}_showDrupalMediaCaption(e,t){const i=this.editor.model.document.selection,n=this.editor.plugins.get("DrupalMediaCaptionEditing"),a=o(i),r=n._getSavedCaption(a)||e.createElement("caption");e.append(r,a),t&&e.setSelection(r,"in")}_hideDrupalMediaCaption(e){const t=this.editor,i=t.model.document.selection,n=t.plugins.get("DrupalMediaCaptionEditing");let a,r=i.getSelectedElement();r?a=re(r):(a=ae(i),r=o(i)),n._saveCaption(r,a),e.setSelection(r,"on"),e.remove(a)}}class se extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionEditing"}constructor(e){super(e),this._savedCaptionsMap=new WeakMap}init(){const e=this.editor,t=e.model.schema;t.isRegistered("caption")?t.extend("caption",{allowIn:"drupalMedia"}):t.register("caption",{allowIn:"drupalMedia",allowContentOf:"$block",isLimit:!0}),e.commands.add("toggleMediaCaption",new oe(e)),this._setupConversion()}_setupConversion(){const e=this.editor,i=e.editing.view;var n;e.conversion.for("upcast").add(function(e){const t=(t,i,n)=>{const{viewItem:a}=i,{writer:r,consumable:o}=n;if(!i.modelRange||!o.consume(a,{attributes:["data-caption"]}))return;const s=r.createElement("caption"),l=i.modelRange.start.nodeAfter,d=e.data.processor.toView(a.getAttribute("data-caption"));n.consumable.constructor.createFrom(d,n.consumable),n.convertChildren(d,s),r.append(s,l)};return e=>{e.on("element:drupal-media",t,{priority:"low"})}}(e)),e.conversion.for("editingDowncast").elementToElement({model:"caption",view:(e,{writer:n})=>{if(!a(e.parent))return null;const r=n.createEditableElement("figcaption");return r.placeholder=Drupal.t("Enter media caption"),(0,V.enablePlaceholder)({view:i,element:r,keepOnFocus:!0}),(0,t.toWidgetEditable)(r,n)}}),e.editing.mapper.on("modelToViewPosition",(n=i,(e,t)=>{const i=t.modelPosition,r=i.parent;if(!a(r))return;const o=t.mapper.toViewElement(r);t.viewPosition=n.createPositionAt(o,i.offset+1)})),e.conversion.for("dataDowncast").add(function(e){return t=>{t.on("insert:caption",((t,i,n)=>{const{consumable:r,writer:o,mapper:s}=n;if(!a(i.item.parent)||!r.consume(i.item,"insert"))return;const l=e.model.createRangeIn(i.item),d=o.createDocumentFragment();s.bindElements(i.item,d);for(const{item:t}of Array.from(l)){const i={item:t,range:e.model.createRangeOn(t)},a=`insert:${t.name||"$text"}`;e.data.downcastDispatcher.fire(a,i,n);for(const a of t.getAttributeKeys())Object.assign(i,{attributeKey:a,attributeOldValue:null,attributeNewValue:i.item.getAttribute(a)}),e.data.downcastDispatcher.fire(`attribute:${a}`,i,n)}for(const e of o.createRangeIn(d).getItems())s.unbindViewElement(e);s.unbindViewElement(d);const u=e.data.processor.toData(d);if(u){const e=s.toViewElement(i.item.parent);o.setAttribute("data-caption",u,e)}}))}}(e))}_getSavedCaption(e){const t=this._savedCaptionsMap.get(e);return t?V.Element.fromJSON(t):null}_saveCaption(e,t){this._savedCaptionsMap.set(e,t.toJSON())}}class le extends e.Plugin{static get requires(){return[]}static get pluginName(){return"DrupalMediaCaptionUI"}init(){const{editor:t}=this,i=t.editing.view;t.ui.componentFactory.add("toggleDrupalMediaCaption",(n=>{const a=new g.ButtonView(n),r=t.commands.get("toggleMediaCaption");return a.set({label:Drupal.t("Caption media"),icon:e.icons.caption,tooltip:!0,isToggleable:!0}),a.bind("isOn","isEnabled").to(r,"value","isEnabled"),a.bind("label").to(r,"value",(e=>e?Drupal.t("Toggle caption off"):Drupal.t("Toggle caption on"))),this.listenTo(a,"execute",(()=>{t.execute("toggleMediaCaption",{focusCaptionOnShow:!0});const e=ae(t.model.document.selection);if(e){const n=t.editing.mapper.toViewElement(e);i.scrollToTheSelection(),i.change((e=>{e.addClass("drupal-media__caption_highlighted",n)}))}t.editing.view.focus()})),a}))}}class de extends e.Plugin{static get requires(){return[se,le]}static get pluginName(){return"DrupalMediaCaption"}}const ue={DrupalMedia:x,MediaImageTextAlternative:A,MediaImageTextAlternativeEditing:y,MediaImageTextAlternativeUi:k,DrupalLinkMedia:O,DrupalMediaCaption:de,DrupalElementStyle:ne}})(),n=n.default})()));
\ No newline at end of file
diff --git a/core/modules/ckeditor5/js/ckeditor5.js b/core/modules/ckeditor5/js/ckeditor5.js
index 3fbb9043b25434011ab45965de33a78123a83b79..8e92ff2843cc288adff99fcbcad710616840b525 100644
--- a/core/modules/ckeditor5/js/ckeditor5.js
+++ b/core/modules/ckeditor5/js/ckeditor5.js
@@ -425,6 +425,11 @@
             element.removeAttribute('required');
           }
 
+          // If the textarea is disabled, enable CKEditor's read-only mode.
+          if (element.hasAttribute('disabled')) {
+            editor.enableReadOnlyMode('ckeditor5_disabled');
+          }
+
           // Integrate CKEditor 5 viewport offset with Drupal displace.
           // @see \Drupal\Tests\ckeditor5\FunctionalJavascript\CKEditor5ToolbarTest
           // @see https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editorui-EditorUI.html#member-viewportOffset
diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/index.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/index.js
index 0033665d56c091bdb04401ac379219b9a07589e4..2e2fe9746769e8dcc923e2df1230ad396ceab039 100644
--- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/index.js
+++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalHtmlEngine/src/index.js
@@ -1,4 +1,4 @@
-// cspell:ignore drupalengine drupalhtmlengine
+// cspell:ignore drupalhtmlengine
 import DrupalHtmlEngine from './drupalhtmlengine';
 
 /**
diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalImage/src/index.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalImage/src/index.js
index 18e04bdbf97b38d53537385ab08da845f65046c0..4f8645cbf90bed44337e312f61beae5d9c766161 100644
--- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalImage/src/index.js
+++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalImage/src/index.js
@@ -1,4 +1,4 @@
-// cspell:ignore imageupload imageresize insertimage drupalimage drupalimageupload drupalimageresize drupalinsertimage
+// cspell:ignore imageupload insertimage drupalimage drupalimageupload drupalinsertimage
 
 import DrupalImage from './drupalimage';
 import DrupalImageUpload from './imageupload/drupalimageupload';
diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js
index 3a4eea1c9fbb90a11f7b147b278c917c5e32bdcc..4e6955c113b65f140f8db9a74af88049d3b54ac1 100644
--- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js
+++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js
@@ -207,10 +207,7 @@ export default class DrupalMediaEditing extends Plugin {
   _defineSchema() {
     const schema = this.editor.model.schema;
     schema.register('drupalMedia', {
-      allowWhere: '$block',
-      isObject: true,
-      isContent: true,
-      isBlock: true,
+      inheritAllFrom: '$blockObject',
       allowAttributes: Object.keys(this.attrs),
     });
     // Register `<drupal-media>` as a block element in the DOM converter. This
diff --git a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/insertdrupalmedia.js b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/insertdrupalmedia.js
index a84b8a267a3d73e272df46fa104978c5ab3329ef..be47f38c313c040005618fd84d3cfd1f912172b1 100644
--- a/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/insertdrupalmedia.js
+++ b/core/modules/ckeditor5/js/ckeditor5_plugins/drupalMedia/src/insertdrupalmedia.js
@@ -86,7 +86,7 @@ export default class InsertDrupalMediaCommand extends Command {
     }
 
     this.editor.model.change((writer) => {
-      this.editor.model.insertContent(
+      this.editor.model.insertObject(
         createDrupalMedia(writer, modelAttributes),
       );
     });
diff --git a/core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/Language.php b/core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/Language.php
index ccbeb9884f91b9de70eadbea27a646d90e8b45f5..3e0ac2a968aa2a814bbeaef69e704c53fe7ada07 100644
--- a/core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/Language.php
+++ b/core/modules/ckeditor5/src/Plugin/CKEditor5Plugin/Language.php
@@ -4,13 +4,19 @@
 
 namespace Drupal\ckeditor5\Plugin\CKEditor5Plugin;
 
+use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableInterface;
 use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableTrait;
 use Drupal\ckeditor5\Plugin\CKEditor5PluginDefault;
-use Drupal\ckeditor5\Plugin\CKEditor5PluginConfigurableInterface;
+use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Language\LanguageManager;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\Url;
 use Drupal\editor\EditorInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * CKEditor 5 Language plugin.
@@ -18,23 +24,72 @@
  * @internal
  *   Plugin classes are internal.
  */
-class Language extends CKEditor5PluginDefault implements CKEditor5PluginConfigurableInterface {
+class Language extends CKEditor5PluginDefault implements CKEditor5PluginConfigurableInterface, ContainerFactoryPluginInterface {
 
   use CKEditor5PluginConfigurableTrait;
 
+  /**
+   * Language constructor.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param \Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The language manager.
+   * @param \Drupal\Core\Routing\RouteProviderInterface $routeProvider
+   *   The route provider.
+   */
+  public function __construct(array $configuration, string $plugin_id, CKEditor5PluginDefinition $plugin_definition, protected LanguageManagerInterface $languageManager, protected RouteProviderInterface $routeProvider) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('language_manager'),
+      $container->get('router.route_provider'),
+    );
+  }
+
   /**
    * {@inheritdoc}
    */
   public function getDynamicPluginConfig(array $static_plugin_config, EditorInterface $editor): array {
-    $predefined_languages = $this->configuration['language_list'] === 'all' ?
-      LanguageManager::getStandardLanguageList() :
-      LanguageManager::getUnitedNationsLanguageList();
+    $languages = NULL;
+    switch ($this->configuration['language_list']) {
+      case 'site_configured':
+        $configured_languages = $this->languageManager->getLanguages();
+        $languages = [];
+        foreach ($configured_languages as $language) {
+          $languages[$language->getId()] = [
+            $language->getName(),
+            '',
+            $language->getDirection(),
+          ];
+        }
+        break;
+
+      case 'all':
+        $languages = LanguageManager::getStandardLanguageList();
+        break;
+
+      case 'un':
+        $languages = LanguageManager::getUnitedNationsLanguageList();
+    }
 
     // Generate the language_list setting as expected by the CKEditor Language
     // plugin, but key the values by the full language name so that we can sort
     // them later on.
     $language_list = [];
-    foreach ($predefined_languages as $langcode => $language) {
+    foreach ($languages as $langcode => $language) {
       $english_name = $language[0];
       $direction = empty($language[2]) ? NULL : $language[2];
       $language_list[$english_name] = [
@@ -60,20 +115,35 @@ public function getDynamicPluginConfig(array $static_plugin_config, EditorInterf
    * @see editor_image_upload_settings_form()
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
-    $predefined_languages = LanguageManager::getStandardLanguageList();
+    $configured = count($this->languageManager->getLanguages());
+    $predefined = count(LanguageManager::getStandardLanguageList());
+    $united_nations = count(LanguageManager::getUnitedNationsLanguageList());
+
+    $language_list_description_args = [
+      ':united-nations-official' => 'https://www.un.org/en/sections/about-un/official-languages',
+      '@count_predefined' => $predefined,
+      '@count_united_nations' => $united_nations,
+      '@count_configured' => $configured,
+    ];
+    // If Language is enabled, link to the configuration route.
+    if ($this->routeProvider->getRoutesByNames(['entity.configurable_language.collection'])) {
+      $language_list_description = $this->t('The list of languages in the CKEditor "Language" dropdown can present the <a href=":united-nations-official">@count_united_nations official languages of the UN</a>, all @count_predefined languages predefined in Drupal, or the <a href=":admin-configure-languages">@count_configured languages configured for this site</a>.', $language_list_description_args + [':admin-configure-languages' => Url::fromRoute('entity.configurable_language.collection')->toString()]);
+    }
+    else {
+      $language_list_description = $this->t('The list of languages in the CKEditor "Language" dropdown can present the <a href=":united-nations-official">@count_united_nations official languages of the UN</a>, all @count_predefined languages predefined in Drupal, or the languages configured for this site.', $language_list_description_args);
+    }
+
     $form['language_list'] = [
       '#title' => $this->t('Language list'),
       '#title_display' => 'invisible',
       '#type' => 'select',
       '#options' => [
-        'un' => $this->t("United Nations' official languages"),
-        'all' => $this->t('All @count languages', ['@count' => count($predefined_languages)]),
+        'un' => $this->t("United Nations' official languages (@count)", ['@count' => $united_nations]),
+        'all' => $this->t('Drupal predefined languages (@count)', ['@count' => $predefined]),
+        'site_configured' => $this->t("Site-configured languages (@count)", ['@count' => $configured]),
       ],
       '#default_value' => $this->configuration['language_list'],
-      '#description' => $this->t('The list of languages to show in the language dropdown. The basic list will only show the <a href=":url">six official languages of the UN</a>. The extended list will show all @count languages that are available in Drupal.', [
-        ':url' => 'https://www.un.org/en/sections/about-un/official-languages',
-        '@count' => count($predefined_languages),
-      ]),
+      '#description' => $language_list_description,
     ];
 
     return $form;
diff --git a/core/modules/ckeditor5/src/Plugin/Validation/Constraint/StyleSensibleElementConstraint.php b/core/modules/ckeditor5/src/Plugin/Validation/Constraint/StyleSensibleElementConstraint.php
index 68d473dd7e8d68807d93d2d99de4d5da6dc2ae86..b4be8e0c6441c7dae1af6e8394554ff20ade36cc 100644
--- a/core/modules/ckeditor5/src/Plugin/Validation/Constraint/StyleSensibleElementConstraint.php
+++ b/core/modules/ckeditor5/src/Plugin/Validation/Constraint/StyleSensibleElementConstraint.php
@@ -4,8 +4,6 @@
 
 namespace Drupal\ckeditor5\Plugin\Validation\Constraint;
 
-// cspell:ignore enableable
-
 use Symfony\Component\Validator\Constraint;
 
 /**
diff --git a/core/modules/ckeditor5/tests/modules/ckeditor5_read_only_mode/ckeditor5_read_only_mode.info.yml b/core/modules/ckeditor5/tests/modules/ckeditor5_read_only_mode/ckeditor5_read_only_mode.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5e4e1f084a2f452fb9544fc0c670d904052b9291
--- /dev/null
+++ b/core/modules/ckeditor5/tests/modules/ckeditor5_read_only_mode/ckeditor5_read_only_mode.info.yml
@@ -0,0 +1,6 @@
+name: CKEditor 5 read-only mode test
+type: module
+description: "Provides code for testing disabled CKEditor 5 editors."
+package: Testing
+dependencies:
+  - ckeditor5:ckeditor5
diff --git a/core/modules/ckeditor5/tests/modules/ckeditor5_read_only_mode/ckeditor5_read_only_mode.module b/core/modules/ckeditor5/tests/modules/ckeditor5_read_only_mode/ckeditor5_read_only_mode.module
new file mode 100644
index 0000000000000000000000000000000000000000..575820d8146405cf66264d21057f70449fa1a2cc
--- /dev/null
+++ b/core/modules/ckeditor5/tests/modules/ckeditor5_read_only_mode/ckeditor5_read_only_mode.module
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Implements hooks for the CKEditor 5 read-only mode test module.
+ */
+
+declare(strict_types = 1);
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Implements hook_form_alter().
+ */
+function ckeditor5_read_only_mode_form_node_page_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void {
+  $form['body']['#disabled'] = \Drupal::state()->get('ckeditor5_read_only_mode_body_enabled', FALSE);
+  $form['field_second_ckeditor5_field']['#disabled'] = \Drupal::state()->get('ckeditor5_read_only_mode_second_ckeditor5_field_enabled', FALSE);
+}
diff --git a/core/modules/ckeditor5/tests/src/Functional/GenericTest.php b/core/modules/ckeditor5/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e58cd064f3d2e074a308cb6643db505af888def
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\ckeditor5\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for ckeditor5.
+ *
+ * @group ckeditor5
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/ckeditor5/tests/src/Functional/Update/CKEditor5UpdateImageToolbarItemTest.php b/core/modules/ckeditor5/tests/src/Functional/Update/CKEditor5UpdateImageToolbarItemTest.php
index 32975e105d2cd7f2a7d5c48f43d64d2c7709ac98..f481448e35e3f5fba7562be281e8db4b00f270d7 100644
--- a/core/modules/ckeditor5/tests/src/Functional/Update/CKEditor5UpdateImageToolbarItemTest.php
+++ b/core/modules/ckeditor5/tests/src/Functional/Update/CKEditor5UpdateImageToolbarItemTest.php
@@ -16,6 +16,7 @@
  * Tests the update path for the CKEditor 5 image toolbar item.
  *
  * @group Update
+ * @group #slow
  */
 class CKEditor5UpdateImageToolbarItemTest extends UpdatePathTestBase {
 
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5AllowedTagsTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5AllowedTagsTest.php
index db6ee3bc44417ea3a44a6e01db7e191b5e0c9713..c7220fc26f5b022566ea13b327395d6e3843bcbc 100644
--- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5AllowedTagsTest.php
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5AllowedTagsTest.php
@@ -7,7 +7,7 @@
 use Drupal\filter\Entity\FilterFormat;
 use Symfony\Component\Yaml\Yaml;
 
-// cspell:ignore esque imageUpload nofilter noeditor sourceediting Editing's
+// cspell:ignore esque imageUpload sourceediting Editing's
 
 /**
  * Tests for CKEditor 5.
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5ReadOnlyModeTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5ReadOnlyModeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea1d4ed68c8dcf65e11a1bdac4ca625a15a35e6e
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5ReadOnlyModeTest.php
@@ -0,0 +1,80 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+
+/**
+ * Tests read-only mode for CKEditor 5.
+ *
+ * @group ckeditor5
+ * @internal
+ */
+class CKEditor5ReadOnlyModeTest extends CKEditor5TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'ckeditor5_read_only_mode',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $field_storage = FieldStorageConfig::create([
+      'field_name' => 'field_second_ckeditor5_field',
+      'entity_type' => 'node',
+      'type' => 'text_with_summary',
+      'cardinality' => 1,
+    ]);
+    $field_storage->save();
+
+    // Attach an instance of the field to the page content type.
+    FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => 'page',
+      'label' => 'Second CKEditor5 field',
+    ])->save();
+    $this->container->get('entity_display.repository')
+      ->getFormDisplay('node', 'page')
+      ->setComponent('field_second_ckeditor5_field', [
+        'type' => 'text_textarea_with_summary',
+      ])
+      ->save();
+  }
+
+  /**
+   * Test that disabling a CKEditor 5 field results in an uneditable editor.
+   */
+  public function testReadOnlyMode() {
+    $page = $this->getSession()->getPage();
+    $assert_session = $this->assertSession();
+    $this->addNewTextFormat($page, $assert_session);
+
+    // Check that both CKEditor 5 fields are editable.
+    $this->drupalGet('node/add');
+    $assert_session->elementAttributeContains('css', '.field--name-body .ck-editor .ck-content', 'contenteditable', 'true');
+    $assert_session->elementAttributeContains('css', '.field--name-field-second-ckeditor5-field .ck-editor .ck-content', 'contenteditable', 'true');
+
+    $this->container->get('state')->set('ckeditor5_read_only_mode_body_enabled', TRUE);
+
+    // Check that the first body field is no longer editable.
+    $this->drupalGet('node/add');
+    $assert_session->elementAttributeContains('css', '.field--name-body .ck-editor .ck-content', 'contenteditable', 'false');
+    $assert_session->elementAttributeContains('css', '.field--name-field-second-ckeditor5-field .ck-editor .ck-content', 'contenteditable', 'true');
+
+    $this->container->get('state')->set('ckeditor5_read_only_mode_second_ckeditor5_field_enabled', TRUE);
+
+    // Both fields are disabled, check that both fields are no longer editable.
+    $this->drupalGet('node/add');
+    $assert_session->elementAttributeContains('css', '.field--name-body .ck-editor .ck-content', 'contenteditable', 'false');
+    $assert_session->elementAttributeContains('css', '.field--name-field-second-ckeditor5-field .ck-editor .ck-content', 'contenteditable', 'false');
+  }
+
+}
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php
index 61b337f1fb6a1ac6555fe9bfea8e81a6d5344b1b..dc56f9bd1b9ce9417d3995f065f935c1284137f2 100644
--- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/CKEditor5Test.php
@@ -7,13 +7,14 @@
 use Drupal\editor\Entity\Editor;
 use Drupal\file\Entity\File;
 use Drupal\filter\Entity\FilterFormat;
+use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\node\Entity\Node;
 use Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
 use Drupal\Tests\TestFileCreationTrait;
 use Drupal\user\RoleInterface;
 use Symfony\Component\Validator\ConstraintViolation;
 
-// cspell:ignore esque splitbutton upcasted sourceediting
+// cspell:ignore esque māori sourceediting splitbutton upcasted
 
 /**
  * Tests for CKEditor 5.
@@ -31,6 +32,7 @@ class CKEditor5Test extends CKEditor5TestBase {
    */
   protected static $modules = [
     'media_library',
+    'language',
   ];
 
   /**
@@ -41,7 +43,7 @@ public function testExistingContent() {
     $assert_session = $this->assertSession();
 
     // Add a node with text rendered via the Plain Text format.
-    $this->drupalGet('node/add');
+    $this->drupalGet('node/add/page');
     $page->fillField('title[0][value]', 'My test content');
     $page->fillField('body[0][value]', '<p>This is test content</p>');
     $page->pressButton('Save');
@@ -103,7 +105,7 @@ function (ConstraintViolation $v) {
       ))
     ));
 
-    $this->drupalGet('node/add');
+    $this->drupalGet('node/add/page');
     $this->waitForEditor();
     $page->fillField('title[0][value]', 'My test content');
 
@@ -147,7 +149,7 @@ public function testHeadingsPlugin() {
     $this->drupalGet('admin/config/content/formats/manage/ckeditor5');
     $this->assertHtmlEsqueFieldValueEquals('filters[filter_html][settings][allowed_html]', '<br> <p> <h2> <h3> <h4> <h5> <h6> <strong> <em>');
 
-    $this->drupalGet('node/add');
+    $this->drupalGet('node/add/page');
     $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-heading-dropdown button'));
 
     $page->find('css', '.ck-heading-dropdown button')->click();
@@ -188,7 +190,7 @@ public function testHeadingsPlugin() {
 
     $page->pressButton('Save configuration');
 
-    $this->drupalGet('node/add');
+    $this->drupalGet('node/add/page');
     $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-heading-dropdown button'));
 
     $page->find('css', '.ck-heading-dropdown button')->click();
@@ -212,12 +214,43 @@ public function testHeadingsPlugin() {
   }
 
   /**
-   * Test for plugin Language of parts.
+   * Test for Language of Parts plugin.
    */
   public function testLanguageOfPartsPlugin() {
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
 
+    $this->languageOfPartsPluginInitialConfigurationHelper($page, $assert_session);
+
+    // Test for "United Nations' official languages" option.
+    $languages = LanguageManager::getUnitedNationsLanguageList();
+    $this->languageOfPartsPluginConfigureLanguageListHelper($page, $assert_session, 'un');
+    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages);
+
+    // Test for "Drupal predefined languages" option.
+    $languages = LanguageManager::getStandardLanguageList();
+    $this->languageOfPartsPluginConfigureLanguageListHelper($page, $assert_session, 'all');
+    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages);
+
+    // Test for "Site-configured languages" option.
+    ConfigurableLanguage::createFromLangcode('ar')->save();
+    ConfigurableLanguage::createFromLangcode('fr')->save();
+    ConfigurableLanguage::createFromLangcode('mi')->setName('Māori')->save();
+    $configured_languages = \Drupal::languageManager()->getLanguages();
+    $languages = [];
+    foreach ($configured_languages as $language) {
+      $language_name = $language->getName();
+      $language_code = $language->getId();
+      $languages[$language_code] = [$language_name];
+    }
+    $this->languageOfPartsPluginConfigureLanguageListHelper($page, $assert_session, 'site_configured');
+    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages);
+  }
+
+  /**
+   * Helper to configure CKEditor5 with Language plugin.
+   */
+  public function languageOfPartsPluginInitialConfigurationHelper($page, $assert_session) {
     $this->createNewTextFormat($page, $assert_session);
     // Press arrow down key to add the button to the active toolbar.
     $this->assertNotEmpty($assert_session->waitForElement('css', '.ckeditor5-toolbar-item-textPartLanguage'));
@@ -248,21 +281,15 @@ public function testLanguageOfPartsPlugin() {
 
     // Confirm there are no longer any warnings.
     $assert_session->waitForElementRemoved('css', '[data-drupal-messages] [role="alert"]');
-
-    // Test for "United Nations' official languages" option.
-    $languages = LanguageManager::getUnitedNationsLanguageList();
-    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages, "un");
-
-    // Test for "All 95 languages" option.
-    $this->drupalGet('admin/config/content/formats/manage/ckeditor5');
-    $languages = LanguageManager::getStandardLanguageList();
-    $this->languageOfPartsPluginTestHelper($page, $assert_session, $languages, "all");
+    $page->pressButton('Save configuration');
+    $assert_session->responseContains('Added text format <em class="placeholder">ckeditor5</em>.');
   }
 
   /**
-   * Validate the available languages on the basis of selected language option.
+   * Helper to set language list option for CKEditor.
    */
-  public function languageOfPartsPluginTestHelper($page, $assert_session, $predefined_languages, $option) {
+  public function languageOfPartsPluginConfigureLanguageListHelper($page, $assert_session, $option) {
+    $this->drupalGet('admin/config/content/formats/manage/ckeditor5');
     $this->assertNotEmpty($assert_session->waitForElement('css', 'a[href^="#edit-editor-settings-plugins-ckeditor5-language"]'));
 
     // Set correct value.
@@ -271,9 +298,14 @@ public function languageOfPartsPluginTestHelper($page, $assert_session, $predefi
     $page->selectFieldOption('editor[settings][plugins][ckeditor5_language][language_list]', $option);
     $assert_session->assertWaitOnAjaxRequest();
     $page->pressButton('Save configuration');
+    $assert_session->responseContains('The text format <em class="placeholder">ckeditor5</em> has been updated.');
+  }
 
-    // Validate plugin on node add page.
-    $this->drupalGet('node/add');
+  /**
+   * Validate expected languages available in editor.
+   */
+  public function languageOfPartsPluginTestHelper($page, $assert_session, $configured_languages) {
+    $this->drupalGet('node/add/page');
     $this->assertNotEmpty($assert_session->waitForText('Choose language'));
 
     // Click on the dropdown button.
@@ -290,13 +322,13 @@ public function languageOfPartsPluginTestHelper($page, $assert_session, $predefi
     foreach ($current_languages as $item) {
       $languages[] = $item->getText();
     }
+
     // Return the values from a single column.
-    $predefined_languages = array_column($predefined_languages, 0);
+    $configured_languages = array_column($configured_languages, 0);
 
     // Sort on full language name.
-    asort($predefined_languages);
-
-    $this->assertSame(array_values($predefined_languages), $languages);
+    asort($configured_languages);
+    $this->assertSame(array_values($configured_languages), $languages);
   }
 
   /**
@@ -518,7 +550,7 @@ public function testEditorFileReferenceIntegration() {
     $assert_session->assertWaitOnAjaxRequest();
     $this->saveNewTextFormat($page, $assert_session);
 
-    $this->drupalGet('node/add');
+    $this->drupalGet('node/add/page');
     $page->fillField('title[0][value]', 'My test content');
 
     // Ensure that CKEditor 5 is focused.
@@ -566,7 +598,7 @@ public function testEmphasis() {
     $assert_session = $this->assertSession();
 
     // Add a node with text rendered via the Plain Text format.
-    $this->drupalGet('node/add');
+    $this->drupalGet('node/add/page');
     $page->fillField('title[0][value]', 'My test content');
     $page->fillField('body[0][value]', '<p>This is a <em>test!</em></p>');
     $page->pressButton('Save');
@@ -624,7 +656,7 @@ function (ConstraintViolation $v) {
     $ordered_list_html = '<ol><li>apple</li><li>banana</li><li>cantaloupe</li></ol>';
     $page = $this->getSession()->getPage();
     $assert_session = $this->assertSession();
-    $this->drupalGet('node/add');
+    $this->drupalGet('node/add/page');
     $page->fillField('title[0][value]', 'My test content');
     $this->pressEditorButton('Source');
     $source_text_area = $assert_session->waitForElement('css', '.ck-source-editing-area textarea');
@@ -690,7 +722,7 @@ public function testFilterHtmlAllowedGlobalAttributes(): void {
     $assert_session = $this->assertSession();
 
     // Add a node with text rendered via the Plain Text format.
-    $this->drupalGet('node/add');
+    $this->drupalGet('node/add/page');
     $page->fillField('title[0][value]', 'Multilingual Hello World');
     // cSpell:disable-next-line
     $page->fillField('body[0][value]', '<p dir="ltr" lang="en">Hello World</p><p dir="rtl" lang="ar">مرحبا بالعالم</p>');
@@ -707,9 +739,8 @@ public function testFilterHtmlAllowedGlobalAttributes(): void {
     $this->waitForEditor();
     $page->pressButton('Save');
 
-    // @todo Remove the expected `xml:lang` attributes in https://www.drupal.org/project/drupal/issues/1333730
     // cSpell:disable-next-line
-    $assert_session->responseContains('<p dir="ltr" lang="en" xml:lang="en">Hello World</p><p dir="rtl" lang="ar" xml:lang="ar">مرحبا بالعالم</p>');
+    $assert_session->responseContains('<p dir="ltr" lang="en">Hello World</p><p dir="rtl" lang="ar">مرحبا بالعالم</p>');
   }
 
 }
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php
index 2701e48dd957e78d9ba4da0fa2d63c06298d41b7..3726b682743432ff495cab7a856c4e23d315fd7c 100644
--- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTest.php
@@ -13,6 +13,7 @@
 /**
  * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Image
  * @group ckeditor5
+ * @group #slow
  * @internal
  */
 class ImageTest extends ImageTestBase {
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTestBase.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTestBase.php
index 8690f0057859f4056a5add794814c53cef991491..0f3d5bb2d332aca98bbc716a8efa4bd77b7d4071 100644
--- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTestBase.php
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageTestBase.php
@@ -9,7 +9,7 @@
 use Drupal\Tests\TestFileCreationTrait;
 use Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
 
-// cspell:ignore imageresize imageupload
+// cspell:ignore imageresize
 
 /**
  * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Image
@@ -55,7 +55,7 @@ abstract class ImageTestBase extends CKEditor5TestBase {
    * @return string[]
    */
   protected function imageAttributes() {
-    return ['src' => '/core/misc/druplicon.png'];
+    return ['src' => base_path() . 'core/misc/druplicon.png'];
   }
 
   /**
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageUrlTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageUrlTest.php
index 6b0e38fc96024cbce66dd7d0ef393c3fa128568b..7e363064d24e01ef63dda4649dee5b3fe84b3be2 100644
--- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageUrlTest.php
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/ImageUrlTest.php
@@ -12,6 +12,7 @@
 /**
  * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Image
  * @group ckeditor5
+ * @group #slow
  * @internal
  */
 class ImageUrlTest extends ImageTestBase {
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaLinkabilityTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaLinkabilityTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ccb58d152cdd0c5336182ea00e10e027e072e407
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaLinkabilityTest.php
@@ -0,0 +1,303 @@
+<?php
+
+namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
+
+use Drupal\editor\Entity\Editor;
+use Drupal\filter\Entity\FilterFormat;
+use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
+use Symfony\Component\Validator\ConstraintViolation;
+
+// cspell:ignore layercake
+
+/**
+ * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media
+ * @group ckeditor5
+ * @group #slow
+ * @internal
+ */
+class MediaLinkabilityTest extends MediaTestBase {
+
+  /**
+   * Ensures arbitrary attributes can be added on links wrapping media via GHS.
+   *
+   * @dataProvider providerLinkability
+   */
+  public function testLinkedMediaArbitraryHtml(bool $unrestricted): void {
+    $assert_session = $this->assertSession();
+
+    $editor = Editor::load('test_format');
+    $settings = $editor->getSettings();
+    $filter_format = $editor->getFilterFormat();
+    if ($unrestricted) {
+      $filter_format
+        ->setFilterConfig('filter_html', ['status' => FALSE]);
+    }
+    else {
+      // Allow the data-foo attribute in <a> via GHS. Also, add support for div's
+      // with data-foo attribute to ensure that linked drupal-media elements can
+      // be wrapped with <div>.
+      $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'] = ['<a data-foo>', '<div data-bar>'];
+      $editor->setSettings($settings);
+      $filter_format->setFilterConfig('filter_html', [
+        'status' => TRUE,
+        'settings' => [
+          'allowed_html' => '<p> <br> <strong> <em> <a href data-foo> <drupal-media data-entity-type data-entity-uuid data-align data-caption alt data-view-mode> <div data-bar>',
+        ],
+      ]);
+    }
+    $editor->save();
+    $filter_format->save();
+    $this->assertSame([], array_map(
+      function (ConstraintViolation $v) {
+        return (string) $v->getMessage();
+      },
+      iterator_to_array(CKEditor5::validatePair(
+        Editor::load('test_format'),
+        FilterFormat::load('test_format')
+      ))
+    ));
+
+    // Wrap the existing drupal-media tag with a div and an a that include
+    // attributes allowed via GHS.
+    $original_value = $this->host->body->value;
+    $this->host->body->value = '<div data-bar="baz"><a href="https://example.com" data-foo="bar">' . $original_value . '</a></div>';
+    $this->host->save();
+    $this->drupalGet($this->host->toUrl('edit-form'));
+
+    // Confirm data-foo is present in the editing view.
+    $this->assertNotEmpty($link = $assert_session->waitForElementVisible('css', 'a[href="https://example.com"]'));
+    $this->assertEquals('bar', $link->getAttribute('data-foo'));
+
+    // Confirm that the media is wrapped by the div on the editing view.
+    $assert_session->elementExists('css', 'div[data-bar="baz"] > .drupal-media > a[href="https://example.com"] > div[data-drupal-media-preview]');
+
+    // Confirm that drupal-media is wrapped by the div and a, and that GHS has
+    // retained arbitrary HTML allowed by source editing.
+    $editor_dom = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertNotEmpty($editor_dom->query('//div[@data-bar="baz"]/a[@data-foo="bar"]/drupal-media'));
+  }
+
+  /**
+   * Tests linkability of the media CKEditor widget.
+   *
+   * Due to the very different HTML markup generated for the editing view and
+   * the data view, this is explicitly testing the "editingDowncast" and
+   * "dataDowncast" results. These are CKEditor 5 concepts.
+   *
+   * @see https://ckeditor.com/docs/ckeditor5/latest/framework/guides/architecture/editing-engine.html#conversion
+   *
+   * @dataProvider providerLinkability
+   */
+  public function testLinkability(bool $unrestricted) {
+    // Disable filter_html.
+    if ($unrestricted) {
+      FilterFormat::load('test_format')
+        ->setFilterConfig('filter_html', ['status' => FALSE])
+        ->save();
+    }
+
+    $page = $this->getSession()->getPage();
+
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $assert_session = $this->assertSession();
+
+    // Initial state: the Drupal Media CKEditor Widget is not selected.
+    $drupalmedia = $assert_session->waitForElementVisible('css', '.ck-content .ck-widget.drupal-media');
+    $this->assertNotEmpty($drupalmedia);
+    $this->assertFalse($drupalmedia->hasClass('.ck-widget_selected'));
+
+    // Assert the "editingDowncast" HTML before making changes.
+    $assert_session->elementExists('css', '.ck-content .ck-widget.drupal-media > [data-drupal-media-preview]');
+
+    // Assert the "dataDowncast" HTML before making changes.
+    $xpath = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertNotEmpty($xpath->query('//drupal-media'));
+    $this->assertEmpty($xpath->query('//a'));
+
+    // Assert the link button is present and not pressed.
+    $link_button = $this->getEditorButton('Link');
+    $this->assertSame('false', $link_button->getAttribute('aria-pressed'));
+
+    // Wait for the preview to load.
+    $preview = $assert_session->waitForElement('css', '.ck-content .ck-widget.drupal-media [data-drupal-media-preview="ready"]');
+    $this->assertNotEmpty($preview);
+
+    // Tests linking Drupal media.
+    $drupalmedia->click();
+    $this->assertTrue($drupalmedia->hasClass('ck-widget_selected'));
+    $this->assertEditorButtonEnabled('Link');
+    // Assert structure of image toolbar balloon.
+    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
+    $link_media_button = $this->getBalloonButton('Link media');
+    // Click the "Link media" button.
+    $this->assertSame('false', $link_media_button->getAttribute('aria-pressed'));
+    $link_media_button->press();
+    // Assert structure of link form balloon.
+    $balloon = $this->assertVisibleBalloon('.ck-link-form');
+    $url_input = $balloon->find('css', '.ck-labeled-field-view__input-wrapper .ck-input-text');
+    // Fill in link form balloon's <input> and hit "Save".
+    $url_input->setValue('http://linking-embedded-media.com');
+    $balloon->pressButton('Save');
+
+    // Assert the "editingDowncast" HTML after making changes. Assert the link
+    // exists, then assert the link exists. Then assert the expected DOM
+    // structure in detail.
+    $assert_session->elementExists('css', '.ck-content a[href="http://linking-embedded-media.com"]');
+    $assert_session->elementExists('css', '.ck-content .drupal-media.ck-widget > a[href="http://linking-embedded-media.com"] > div[aria-label] > article > div > img[src*="image-test.png"]');
+
+    // Assert the "dataDowncast" HTML after making changes.
+    $xpath = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertNotEmpty($xpath->query('//drupal-media'));
+    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]'));
+    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]/drupal-media'));
+    // Ensure that the media caption is retained and not linked as a result of
+    // linking media.
+    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]/drupal-media[@data-caption="baz"]'));
+
+    // Add `class="trusted"` to the link.
+    $this->assertEmpty($xpath->query('//a[@href="http://linking-embedded-media.com" and @class="trusted"]'));
+    $this->pressEditorButton('Source');
+    $source_text_area = $assert_session->waitForElement('css', '.ck-source-editing-area textarea');
+    $this->assertNotEmpty($source_text_area);
+    $new_value = str_replace('<a ', '<a class="trusted" ', $source_text_area->getValue());
+    $source_text_area->setValue('<p>temp</p>');
+    $source_text_area->setValue($new_value);
+    $this->pressEditorButton('Source');
+
+    // When unrestricted, additional attributes on links should be retained.
+    $xpath = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertCount($unrestricted ? 1 : 0, $xpath->query('//a[@href="http://linking-embedded-media.com" and @class="trusted"]'));
+
+    // Save the entity whose text field is being edited.
+    $page->pressButton('Save');
+
+    // Assert the HTML the end user sees.
+    $assert_session->elementExists('css', $unrestricted
+      ? 'a[href="http://linking-embedded-media.com"].trusted img[src*="image-test.png"]'
+      : 'a[href="http://linking-embedded-media.com"] img[src*="image-test.png"]');
+
+    // Go back to edit the now *linked* <drupal-media>. Everything from this
+    // point onwards is effectively testing "upcasting" and proving there is no
+    // data loss.
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+
+    // Assert the "dataDowncast" HTML before making changes.
+    $xpath = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertNotEmpty($xpath->query('//drupal-media'));
+    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]'));
+    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]/drupal-media'));
+
+    // Tests unlinking media.
+    $drupalmedia->click();
+    $this->assertEditorButtonEnabled('Link');
+    $this->assertSame('true', $this->getEditorButton('Link')->getAttribute('aria-pressed'));
+    // Assert structure of Drupal media toolbar balloon.
+    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
+    $link_media_button = $this->getBalloonButton('Link media');
+    $this->assertSame('true', $link_media_button->getAttribute('aria-pressed'));
+    $link_media_button->click();
+    // Assert structure of link actions balloon.
+    $this->getBalloonButton('Edit link');
+    $unlink_image_button = $this->getBalloonButton('Unlink');
+    // Click the "Unlink" button.
+    $unlink_image_button->click();
+    $this->assertSame('false', $this->getEditorButton('Link')->getAttribute('aria-pressed'));
+
+    // Assert the "editingDowncast" HTML after making changes. Assert the link
+    // exists, then assert no link exists. Then assert the expected DOM
+    // structure in detail.
+    $assert_session->elementNotExists('css', '.ck-content a');
+    $assert_session->elementExists('css', '.ck-content .drupal-media.ck-widget > div[aria-label] > article > div > img[src*="image-test.png"]');
+
+    // Ensure that figcaption exists.
+    // @see https://www.drupal.org/project/drupal/issues/3268318
+    $assert_session->elementExists('css', '.ck-content .drupal-media.ck-widget > figcaption');
+
+    // Assert the "dataDowncast" HTML after making changes.
+    $xpath = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertNotEmpty($xpath->query('//drupal-media'));
+    $this->assertEmpty($xpath->query('//a'));
+  }
+
+  public function providerLinkability(): array {
+    return [
+      'restricted' => [FALSE],
+      'unrestricted' => [TRUE],
+    ];
+  }
+
+  /**
+   * Ensure that manual link decorators work with linkable media.
+   *
+   * @dataProvider providerLinkability
+   */
+  public function testLinkManualDecorator(bool $unrestricted) {
+    \Drupal::service('module_installer')->install(['ckeditor5_manual_decorator_test']);
+    $this->resetAll();
+
+    $decorator = 'Open in a new tab';
+    $decorator_attributes = '[@target="_blank"][@rel="noopener noreferrer"][@class="link-new-tab"]';
+
+    // Disable filter_html.
+    if ($unrestricted) {
+      FilterFormat::load('test_format')
+        ->setFilterConfig('filter_html', ['status' => FALSE])
+        ->save();
+      $decorator = 'Pink color';
+      $decorator_attributes = '[@style="color:pink;"]';
+    }
+
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->assertNotEmpty($drupalmedia = $assert_session->waitForElementVisible('css', '.ck-content .ck-widget.drupal-media'));
+    $drupalmedia->click();
+    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
+
+    // Turn off caption, so we don't accidentally put our link in that text
+    // field instead of on the actual media.
+    $this->getBalloonButton('Toggle caption off')->click();
+    $assert_session->assertNoElementAfterWait('css', 'figure.drupal-media > figcaption');
+
+    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
+    $this->getBalloonButton('Link media')->click();
+
+    $balloon = $this->assertVisibleBalloon('.ck-link-form');
+    $url_input = $balloon->find('css', '.ck-labeled-field-view__input-wrapper .ck-input-text');
+    $url_input->setValue('http://linking-embedded-media.com');
+    $this->getBalloonButton($decorator)->click();
+    $balloon->pressButton('Save');
+
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.drupal-media a'));
+    $this->assertVisibleBalloon('.ck-link-actions');
+
+    $xpath = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertNotEmpty($xpath->query("//a[@href='http://linking-embedded-media.com']$decorator_attributes"));
+    $this->assertNotEmpty($xpath->query("//a[@href='http://linking-embedded-media.com']$decorator_attributes/drupal-media"));
+
+    // Ensure that manual decorators upcast correctly.
+    $page->pressButton('Save');
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->assertNotEmpty($drupalmedia = $assert_session->waitForElementVisible('css', '.ck-content .ck-widget.drupal-media'));
+    $xpath = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertNotEmpty($xpath->query("//a[@href='http://linking-embedded-media.com']$decorator_attributes"));
+    $this->assertNotEmpty($xpath->query("//a[@href='http://linking-embedded-media.com']$decorator_attributes/drupal-media"));
+
+    // Finally, ensure that media can be unlinked.
+    $drupalmedia->click();
+    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
+    $this->getBalloonButton('Link media')->click();
+    $this->assertVisibleBalloon('.ck-link-actions');
+    $this->getBalloonButton('Unlink')->click();
+
+    $this->assertTrue($assert_session->waitForElementRemoved('css', '.drupal-media a'));
+    $xpath = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertEmpty($xpath->query('//a'));
+    $this->assertNotEmpty($xpath->query('//drupal-media'));
+  }
+
+}
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaPreviewTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaPreviewTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c321f244239c894745661d3fcc85e951f7a9525d
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaPreviewTest.php
@@ -0,0 +1,226 @@
+<?php
+
+namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\filter\Entity\FilterFormat;
+
+/**
+ * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media
+ * @group ckeditor5
+ * @group #slow
+ * @internal
+ */
+class MediaPreviewTest extends MediaTestBase {
+
+  /**
+   * Tests that failed media embed preview requests inform the end user.
+   */
+  public function testErrorMessages() {
+    // This test currently frequently causes the SQLite database to lock, so
+    // skip the test on SQLite until the issue can be resolved.
+    // @todo https://www.drupal.org/project/drupal/issues/3273626
+    if (Database::getConnection()->driver() === 'sqlite') {
+      $this->markTestSkipped('Test frequently causes a locked database on SQLite');
+    }
+
+    // Assert that a request to the `media.filter.preview` route that does not
+    // result in a 200 response (due to server error or network error) is
+    // handled in the JavaScript by displaying the expected error message.
+    // @see core/modules/media/js/media_embed_ckeditor.theme.js
+    // @see js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js
+    $this->container->get('state')->set('test_media_filter_controller_throw_error', TRUE);
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $assert_session = $this->assertSession();
+    $assert_session->waitForElementVisible('css', '.ck-widget.drupal-media');
+    $this->assertEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]', 1000));
+    $assert_session->elementNotExists('css', '.ck-widget.drupal-media .media');
+    $this->assertNotEmpty($assert_session->waitForText('An error occurred while trying to preview the media. Save your work and reload this page.'));
+    // Now assert that the error doesn't appear when the override to force an
+    // error is removed.
+    $this->container->get('state')->set('test_media_filter_controller_throw_error', FALSE);
+    $this->getSession()->reload();
+    $this->waitForEditor();
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
+
+    // There's a second kind of error message that comes from the back end
+    // that happens when the media uuid can't be converted to a media preview.
+    // In this case, the error will appear in a the themeable
+    // media-embed-error.html template.  We have a hook altering the css
+    // classes to test the twig template is working properly and picking up our
+    // extra class.
+    // @see \Drupal\media\Plugin\Filter\MediaEmbed::renderMissingMediaIndicator()
+    // @see core/modules/media/templates/media-embed-error.html.twig
+    // @see media_test_embed_preprocess_media_embed_error()
+    $original_value = $this->host->body->value;
+    $this->host->body->value = str_replace($this->media->uuid(), 'invalid_uuid', $original_value);
+    $this->host->save();
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-widget.drupal-media .this-error-message-is-themeable'));
+
+    // Test when using the starterkit_theme theme, an additional class is added
+    // to the error, which is supported by
+    // stable9/templates/content/media-embed-error.html.twig.
+    $this->assertTrue($this->container->get('theme_installer')->install(['starterkit_theme']));
+    $this->config('system.theme')
+      ->set('default', 'starterkit_theme')
+      ->save();
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-widget.drupal-media .this-error-message-is-themeable'));
+
+    // Test that restoring a valid UUID results in the media embed preview
+    // displaying.
+    $this->host->body->value = $original_value;
+    $this->host->save();
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
+    $assert_session->elementNotExists('css', '.ck-widget.drupal-media .this-error-message-is-themeable');
+  }
+
+  /**
+   * The CKEditor Widget must load a preview generated using the default theme.
+   */
+  public function testPreviewUsesDefaultThemeAndIsClientCacheable() {
+    // Make the node edit form use the admin theme, like on most Drupal sites.
+    $this->config('node.settings')
+      ->set('use_admin_theme', TRUE)
+      ->save();
+
+    // Allow the test user to view the admin theme.
+    $this->adminUser->addRole($this->drupalCreateRole(['view the administration theme']));
+    $this->adminUser->save();
+
+    // Configure a different default and admin theme, like on most Drupal sites.
+    $this->config('system.theme')
+      ->set('default', 'stable9')
+      ->set('admin', 'starterkit_theme')
+      ->save();
+
+    // Assert that when looking at an embedded entity in the CKEditor Widget,
+    // the preview is generated using the default theme, not the admin theme.
+    // @see media_test_embed_entity_view_alter()
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $assert_session = $this->assertSession();
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
+    $element = $assert_session->elementExists('css', '[data-media-embed-test-active-theme]');
+    $this->assertSame('stable9', $element->getAttribute('data-media-embed-test-active-theme'));
+    // Assert that the first preview request transferred >500 B over the wire.
+    // Then toggle source mode on and off. This causes the CKEditor widget to be
+    // destroyed and then reconstructed. Assert that during this reconstruction,
+    // a second request is sent. This second request should have transferred 0
+    // bytes: the browser should have cached the response, thus resulting in a
+    // much better user experience.
+    $this->assertGreaterThan(500, $this->getLastPreviewRequestTransferSize());
+    $this->pressEditorButton('Source');
+    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-source-editing-area'));
+    // CKEditor 5 is very smart: if no changes were made in the Source Editing
+    // Area, it will not rerender the contents. In this test, we
+    // want to verify that Media preview responses are cached on the client side
+    // so it is essential that rerendering occurs. To achieve this, we append a
+    // single space.
+    $source_text_area = $this->getSession()->getPage()->find('css', '[name="body[0][value]"] + .ck-editor textarea');
+    $source_text_area->setValue($source_text_area->getValue() . ' ');
+    $this->pressEditorButton('Source');
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
+    $this->assertSame(0, $this->getLastPreviewRequestTransferSize());
+  }
+
+  /**
+   * Tests preview route access.
+   *
+   * @param bool $media_embed_enabled
+   *   Whether to test with media_embed filter enabled on the text format.
+   * @param bool $can_use_format
+   *   Whether the logged in user is allowed to use the text format.
+   *
+   * @dataProvider previewAccessProvider
+   */
+  public function testEmbedPreviewAccess($media_embed_enabled, $can_use_format) {
+    // Reconfigure the host entity's text format to suit our needs.
+    /** @var \Drupal\filter\FilterFormatInterface $format */
+    $format = FilterFormat::load($this->host->body->format);
+    $format->set('filters', [
+      'filter_align' => ['status' => TRUE],
+      'filter_caption' => ['status' => TRUE],
+      'media_embed' => ['status' => $media_embed_enabled],
+    ]);
+    $format->save();
+
+    $permissions = [
+      'bypass node access',
+    ];
+    if ($can_use_format) {
+      $permissions[] = $format->getPermissionName();
+    }
+    $this->drupalLogin($this->drupalCreateUser($permissions));
+    $this->drupalGet($this->host->toUrl('edit-form'));
+
+    $assert_session = $this->assertSession();
+    if ($can_use_format) {
+      $this->waitForEditor();
+      if ($media_embed_enabled) {
+        // The preview rendering, which in this test will use Starterkit theme's
+        // media.html.twig template, will fail without the CSRF token/header.
+        // @see ::testEmbeddedMediaPreviewWithCsrfToken()
+        $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'article.media'));
+      }
+      else {
+        // If the filter isn't enabled, there won't be an error, but the
+        // preview shouldn't be rendered.
+        $assert_session->assertWaitOnAjaxRequest();
+        $assert_session->elementNotExists('css', 'article.media');
+      }
+    }
+    else {
+      $assert_session->pageTextContains('This field has been disabled because you do not have sufficient permissions to edit it.');
+    }
+  }
+
+  /**
+   * Data provider for ::testEmbedPreviewAccess.
+   */
+  public function previewAccessProvider() {
+    return [
+      'media_embed filter enabled' => [
+        TRUE,
+        TRUE,
+      ],
+      'media_embed filter disabled' => [
+        FALSE,
+        TRUE,
+      ],
+      'media_embed filter enabled, user not allowed to use text format' => [
+        TRUE,
+        FALSE,
+      ],
+    ];
+  }
+
+  /**
+   * Ensure media preview isn't clickable.
+   */
+  public function testMediaPointerEvent() {
+    $entityViewDisplay = EntityViewDisplay::load('media.image.view_mode_1');
+    $thumbnail = $entityViewDisplay->getComponent('thumbnail');
+    $thumbnail['settings']['image_link'] = 'file';
+    $entityViewDisplay->setComponent('thumbnail', $thumbnail);
+    $entityViewDisplay->save();
+
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $url = $this->host->toUrl('edit-form');
+    $this->drupalGet($url);
+    $this->waitForEditor();
+    $assert_session->waitForLink('default alt');
+    $page->find('css', '.ck .drupal-media')->click();
+    // Assert that the media preview is not clickable by comparing the URL.
+    $this->assertEquals($url->toString(), $this->getUrl());
+  }
+
+}
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTest.php
index 3f7590c9f15acbe7c2da09372597617f8066d10b..05d535b6ea183f7f0cb5e837552f6ea167e96669 100644
--- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTest.php
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTest.php
@@ -4,19 +4,13 @@
 
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\Core\Entity\Entity\EntityViewMode;
-use Drupal\Core\Database\Database;
+use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
 use Drupal\editor\Entity\Editor;
 use Drupal\field\Entity\FieldConfig;
-use Drupal\file\Entity\File;
 use Drupal\filter\Entity\FilterFormat;
-use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\language\Entity\ContentLanguageSettings;
 use Drupal\media\Entity\Media;
-use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
-use Drupal\Tests\TestFileCreationTrait;
-use Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
-use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
 use Symfony\Component\Validator\ConstraintViolation;
@@ -26,209 +20,10 @@
 /**
  * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\Media
  * @group ckeditor5
+ * @group #slow
  * @internal
  */
-class MediaTest extends WebDriverTestBase {
-
-  use CKEditor5TestTrait;
-  use MediaTypeCreationTrait;
-  use TestFileCreationTrait;
-
-  /**
-   * The user to use during testing.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $adminUser;
-
-  /**
-   * The sample Media entity to embed.
-   *
-   * @var \Drupal\media\MediaInterface
-   */
-  protected $media;
-
-  /**
-   * The second sample Media entity to embed used in one of the tests.
-   *
-   * @var \Drupal\media\MediaInterface
-   */
-  protected $mediaFile;
-
-  /**
-   * A host entity with a body field to embed media in.
-   *
-   * @var \Drupal\node\NodeInterface
-   */
-  protected $host;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = [
-    'ckeditor5',
-    'media',
-    'node',
-    'text',
-    'media_test_embed',
-    'media_library',
-    'ckeditor5_test',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $defaultTheme = 'starterkit_theme';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    EntityViewMode::create([
-      'id' => 'media.view_mode_1',
-      'targetEntityType' => 'media',
-      'status' => TRUE,
-      'enabled' => TRUE,
-      'label' => 'View Mode 1',
-    ])->save();
-    EntityViewMode::create([
-      'id' => 'media.22222',
-      'targetEntityType' => 'media',
-      'status' => TRUE,
-      'enabled' => TRUE,
-      'label' => 'View Mode 2 has Numeric ID',
-    ])->save();
-    FilterFormat::create([
-      'format' => 'test_format',
-      'name' => 'Test format',
-      'filters' => [
-        'filter_html' => [
-          'status' => TRUE,
-          'settings' => [
-            'allowed_html' => '<p> <br> <strong> <em> <a href> <drupal-media data-entity-type data-entity-uuid data-align data-view-mode data-caption alt>',
-          ],
-        ],
-        'filter_align' => ['status' => TRUE],
-        'filter_caption' => ['status' => TRUE],
-        'media_embed' => [
-          'status' => TRUE,
-          'settings' => [
-            'default_view_mode' => 'view_mode_1',
-            'allowed_view_modes' => [
-              'view_mode_1' => 'view_mode_1',
-              '22222' => '22222',
-            ],
-            'allowed_media_types' => [],
-          ],
-        ],
-      ],
-    ])->save();
-    Editor::create([
-      'editor' => 'ckeditor5',
-      'format' => 'test_format',
-      'settings' => [
-        'toolbar' => [
-          'items' => [
-            'sourceEditing',
-            'link',
-            'bold',
-            'italic',
-          ],
-        ],
-        'plugins' => [
-          'ckeditor5_sourceEditing' => [
-            'allowed_tags' => [],
-          ],
-          'media_media' => [
-            'allow_view_mode_override' => TRUE,
-          ],
-        ],
-      ],
-      'image_upload' => [
-        'status' => FALSE,
-      ],
-    ])->save();
-    $this->assertSame([], array_map(
-      function (ConstraintViolation $v) {
-        return (string) $v->getMessage();
-      },
-      iterator_to_array(CKEditor5::validatePair(
-        Editor::load('test_format'),
-        FilterFormat::load('test_format')
-      ))
-    ));
-
-    // Note that media_install() grants 'view media' to all users by default.
-    $this->adminUser = $this->drupalCreateUser([
-      'use text format test_format',
-      'bypass node access',
-    ]);
-
-    // Create a sample media entity to be embedded.
-    $this->createMediaType('image', ['id' => 'image']);
-    File::create([
-      'uri' => $this->getTestFiles('image')[0]->uri,
-    ])->save();
-    $this->media = Media::create([
-      'bundle' => 'image',
-      'name' => 'Screaming hairy armadillo',
-      'field_media_image' => [
-        [
-          'target_id' => 1,
-          'alt' => 'default alt',
-          'title' => 'default title',
-        ],
-      ],
-    ]);
-    $this->media->save();
-
-    $this->createMediaType('file', ['id' => 'file']);
-    File::create([
-      'uri' => $this->getTestFiles('text')[0]->uri,
-    ])->save();
-    $this->mediaFile = Media::create([
-      'bundle' => 'file',
-      'name' => 'Information about screaming hairy armadillo',
-      'field_media_file' => [
-        [
-          'target_id' => 2,
-        ],
-      ],
-    ]);
-    $this->mediaFile->save();
-
-    // Set created media types for each view mode.
-    EntityViewDisplay::create([
-      'id' => 'media.image.view_mode_1',
-      'targetEntityType' => 'media',
-      'status' => TRUE,
-      'bundle' => 'image',
-      'mode' => 'view_mode_1',
-    ])->save();
-    EntityViewDisplay::create([
-      'id' => 'media.image.22222',
-      'targetEntityType' => 'media',
-      'status' => TRUE,
-      'bundle' => 'image',
-      'mode' => '22222',
-    ])->save();
-
-    // Create a sample host entity to embed media in.
-    $this->drupalCreateContentType(['type' => 'blog']);
-    $this->host = $this->createNode([
-      'type' => 'blog',
-      'title' => 'Animals with strange names',
-      'body' => [
-        'value' => '<drupal-media data-entity-type="media" data-entity-uuid="' . $this->media->uuid() . '" data-caption="baz"></drupal-media>',
-        'format' => 'test_format',
-      ],
-    ]);
-    $this->host->save();
-
-    $this->drupalLogin($this->adminUser);
-  }
+class MediaTest extends MediaTestBase {
 
   /**
    * Tests that `<drupal-media>` is converted into a block element.
@@ -276,29 +71,33 @@ public function testOnlyDrupalMediaTagProcessed() {
   }
 
   /**
-   * Tests that arbitrary attributes are allowed via GHS.
+   * Tests adding media to a list does not split the list.
    */
-  public function testMediaArbitraryHtml() {
+  public function testMediaSplitList() {
     $assert_session = $this->assertSession();
 
     $editor = Editor::load('test_format');
     $settings = $editor->getSettings();
 
-    // Allow the data-foo attribute in drupal-media via GHS. Also, add support
-    // for div's with data-foo attribute to ensure that drupal-media elements
-    // can be wrapped with other block elements.
-    $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'] = ['<drupal-media data-foo>', '<div data-bar>'];
+    // Add lists to the editor.
+    $settings['plugins']['ckeditor5_list'] = [
+      'reversed' => FALSE,
+      'startIndex' => FALSE,
+    ];
+    $settings['toolbar']['items'] = array_merge($settings['toolbar']['items'], ['bulletedList', 'numberedList']);
     $editor->setSettings($settings);
     $editor->save();
 
+    // Add lists to the filter.
     $filter_format = $editor->getFilterFormat();
     $filter_format->setFilterConfig('filter_html', [
       'status' => TRUE,
       'settings' => [
-        'allowed_html' => '<p> <br> <strong> <em> <a href> <drupal-media data-entity-type data-entity-uuid data-align data-caption alt data-foo data-view-mode> <div data-bar>',
+        'allowed_html' => '<p> <br> <strong> <em> <a href> <drupal-media data-entity-type data-entity-uuid data-align data-caption alt data-view-mode> <ol> <ul> <li>',
       ],
     ]);
     $filter_format->save();
+
     $this->assertSame([], array_map(
       function (ConstraintViolation $v) {
         return (string) $v->getMessage();
@@ -309,58 +108,49 @@ function (ConstraintViolation $v) {
       ))
     ));
 
-    // Add data-foo use to an existing drupal-media tag.
+    // Wrap the media with a list item.
     $original_value = $this->host->body->value;
-    $this->host->body->value = '<div data-bar="baz">' . str_replace('drupal-media', 'drupal-media data-foo="bar" ', $original_value) . '</div>';
+    $this->host->body->value = '<ol><li>' . $original_value . '</li></ol>';
     $this->host->save();
     $this->drupalGet($this->host->toUrl('edit-form'));
 
-    // Confirm data-foo is present in the drupal-media preview.
     $this->assertNotEmpty($upcasted_media = $assert_session->waitForElementVisible('css', '.ck-widget.drupal-media'));
-    $this->assertFalse($upcasted_media->hasAttribute('data-foo'));
-    $this->assertNotEmpty($preview = $assert_session->waitForElementVisible('css', '.ck-widget.drupal-media > [data-drupal-media-preview="ready"] > .media', 30000));
-    $this->assertEquals('bar', $preview->getAttribute('data-foo'));
 
-    // Confirm that the media is wrapped by the div on the editing view.
-    $assert_session->elementExists('css', 'div[data-bar="baz"] > .drupal-media');
-
-    // Confirm data-foo is not stripped from source.
-    $this->assertSourceAttributeSame('data-foo', 'bar');
+    // Confirm the media is wrapped by the list item on the editing view.
+    $assert_session->elementExists('css', 'li > .drupal-media');
+    // Confirm the media is not adjacent to the list on the editing view.
+    $assert_session->elementNotExists('css', 'ol + .drupal-media');
 
-    // Confirm that drupal-media is wrapped by the div.
     $editor_dom = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertNotEmpty($editor_dom->query('//div[@data-bar="baz"]/drupal-media'));
+    // Confirm drupal-media is wrapped by the list item.
+    $this->assertNotEmpty($editor_dom->query('//li/drupal-media'));
+    // Confirm the media is not adjacent to the list.
+    $this->assertEmpty($editor_dom->query('//ol/following-sibling::drupal-media'));
   }
 
   /**
-   * Ensures arbitrary attributes can be added on links wrapping media via GHS.
-   *
-   * @dataProvider providerLinkability
+   * Tests that arbitrary attributes are allowed via GHS.
    */
-  public function testLinkedMediaArbitraryHtml(bool $unrestricted): void {
+  public function testMediaArbitraryHtml() {
     $assert_session = $this->assertSession();
 
     $editor = Editor::load('test_format');
     $settings = $editor->getSettings();
-    $filter_format = $editor->getFilterFormat();
-    if ($unrestricted) {
-      $filter_format
-        ->setFilterConfig('filter_html', ['status' => FALSE]);
-    }
-    else {
-      // Allow the data-foo attribute in <a> via GHS. Also, add support for div's
-      // with data-foo attribute to ensure that linked drupal-media elements can
-      // be wrapped with <div>.
-      $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'] = ['<a data-foo>', '<div data-bar>'];
-      $editor->setSettings($settings);
-      $filter_format->setFilterConfig('filter_html', [
-        'status' => TRUE,
-        'settings' => [
-          'allowed_html' => '<p> <br> <strong> <em> <a href data-foo> <drupal-media data-entity-type data-entity-uuid data-align data-caption alt data-view-mode> <div data-bar>',
-        ],
-      ]);
-    }
+
+    // Allow the data-foo attribute in drupal-media via GHS. Also, add support
+    // for div's with data-foo attribute to ensure that drupal-media elements
+    // can be wrapped with other block elements.
+    $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'] = ['<drupal-media data-foo>', '<div data-bar>'];
+    $editor->setSettings($settings);
     $editor->save();
+
+    $filter_format = $editor->getFilterFormat();
+    $filter_format->setFilterConfig('filter_html', [
+      'status' => TRUE,
+      'settings' => [
+        'allowed_html' => '<p> <br> <strong> <em> <a href> <drupal-media data-entity-type data-entity-uuid data-align data-caption alt data-foo data-view-mode> <div data-bar>',
+      ],
+    ]);
     $filter_format->save();
     $this->assertSame([], array_map(
       function (ConstraintViolation $v) {
@@ -372,141 +162,27 @@ function (ConstraintViolation $v) {
       ))
     ));
 
-    // Wrap the existing drupal-media tag with a div and an a that include
-    // attributes allowed via GHS.
+    // Add data-foo use to an existing drupal-media tag.
     $original_value = $this->host->body->value;
-    $this->host->body->value = '<div data-bar="baz"><a href="https://example.com" data-foo="bar">' . $original_value . '</a></div>';
+    $this->host->body->value = '<div data-bar="baz">' . str_replace('drupal-media', 'drupal-media data-foo="bar" ', $original_value) . '</div>';
     $this->host->save();
     $this->drupalGet($this->host->toUrl('edit-form'));
 
-    // Confirm data-foo is present in the editing view.
-    $this->assertNotEmpty($link = $assert_session->waitForElementVisible('css', 'a[href="https://example.com"]'));
-    $this->assertEquals('bar', $link->getAttribute('data-foo'));
+    // Confirm data-foo is present in the drupal-media preview.
+    $this->assertNotEmpty($upcasted_media = $assert_session->waitForElementVisible('css', '.ck-widget.drupal-media'));
+    $this->assertFalse($upcasted_media->hasAttribute('data-foo'));
+    $this->assertNotEmpty($preview = $assert_session->waitForElementVisible('css', '.ck-widget.drupal-media > [data-drupal-media-preview="ready"] > .media', 30000));
+    $this->assertEquals('bar', $preview->getAttribute('data-foo'));
 
     // Confirm that the media is wrapped by the div on the editing view.
-    $assert_session->elementExists('css', 'div[data-bar="baz"] > .drupal-media > a[href="https://example.com"] > div[data-drupal-media-preview]');
-
-    // Confirm that drupal-media is wrapped by the div and a, and that GHS has
-    // retained arbitrary HTML allowed by source editing.
-    $editor_dom = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertNotEmpty($editor_dom->query('//div[@data-bar="baz"]/a[@data-foo="bar"]/drupal-media'));
-  }
-
-  /**
-   * Tests that failed media embed preview requests inform the end user.
-   */
-  public function testErrorMessages() {
-    // This test currently frequently causes the SQLite database to lock, so
-    // skip the test on SQLite until the issue can be resolved.
-    // @todo https://www.drupal.org/project/drupal/issues/3273626
-    if (Database::getConnection()->driver() === 'sqlite') {
-      $this->markTestSkipped('Test frequently causes a locked database on SQLite');
-    }
-
-    // Assert that a request to the `media.filter.preview` route that does not
-    // result in a 200 response (due to server error or network error) is
-    // handled in the JavaScript by displaying the expected error message.
-    // @see core/modules/media/js/media_embed_ckeditor.theme.js
-    // @see js/ckeditor5_plugins/drupalMedia/src/drupalmediaediting.js
-    $this->container->get('state')->set('test_media_filter_controller_throw_error', TRUE);
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $assert_session = $this->assertSession();
-    $assert_session->waitForElementVisible('css', '.ck-widget.drupal-media');
-    $this->assertEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]', 1000));
-    $assert_session->elementNotExists('css', '.ck-widget.drupal-media .media');
-    $this->assertNotEmpty($assert_session->waitForText('An error occurred while trying to preview the media. Save your work and reload this page.'));
-    // Now assert that the error doesn't appear when the override to force an
-    // error is removed.
-    $this->container->get('state')->set('test_media_filter_controller_throw_error', FALSE);
-    $this->getSession()->reload();
-    $this->waitForEditor();
-    $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
-
-    // There's a second kind of error message that comes from the back end
-    // that happens when the media uuid can't be converted to a media preview.
-    // In this case, the error will appear in a the themeable
-    // media-embed-error.html template.  We have a hook altering the css
-    // classes to test the twig template is working properly and picking up our
-    // extra class.
-    // @see \Drupal\media\Plugin\Filter\MediaEmbed::renderMissingMediaIndicator()
-    // @see core/modules/media/templates/media-embed-error.html.twig
-    // @see media_test_embed_preprocess_media_embed_error()
-    $original_value = $this->host->body->value;
-    $this->host->body->value = str_replace($this->media->uuid(), 'invalid_uuid', $original_value);
-    $this->host->save();
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-widget.drupal-media .this-error-message-is-themeable'));
-
-    // Test when using the starterkit_theme theme, an additional class is added
-    // to the error, which is supported by
-    // stable9/templates/content/media-embed-error.html.twig.
-    $this->assertTrue($this->container->get('theme_installer')->install(['starterkit_theme']));
-    $this->config('system.theme')
-      ->set('default', 'starterkit_theme')
-      ->save();
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-widget.drupal-media .this-error-message-is-themeable'));
-
-    // Test that restoring a valid UUID results in the media embed preview
-    // displaying.
-    $this->host->body->value = $original_value;
-    $this->host->save();
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
-    $assert_session->elementNotExists('css', '.ck-widget.drupal-media .this-error-message-is-themeable');
-  }
-
-  /**
-   * The CKEditor Widget must load a preview generated using the default theme.
-   */
-  public function testPreviewUsesDefaultThemeAndIsClientCacheable() {
-    // Make the node edit form use the admin theme, like on most Drupal sites.
-    $this->config('node.settings')
-      ->set('use_admin_theme', TRUE)
-      ->save();
-
-    // Allow the test user to view the admin theme.
-    $this->adminUser->addRole($this->drupalCreateRole(['view the administration theme']));
-    $this->adminUser->save();
+    $assert_session->elementExists('css', 'div[data-bar="baz"] > .drupal-media');
 
-    // Configure a different default and admin theme, like on most Drupal sites.
-    $this->config('system.theme')
-      ->set('default', 'stable9')
-      ->set('admin', 'starterkit_theme')
-      ->save();
+    // Confirm data-foo is not stripped from source.
+    $this->assertSourceAttributeSame('data-foo', 'bar');
 
-    // Assert that when looking at an embedded entity in the CKEditor Widget,
-    // the preview is generated using the default theme, not the admin theme.
-    // @see media_test_embed_entity_view_alter()
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $assert_session = $this->assertSession();
-    $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
-    $element = $assert_session->elementExists('css', '[data-media-embed-test-active-theme]');
-    $this->assertSame('stable9', $element->getAttribute('data-media-embed-test-active-theme'));
-    // Assert that the first preview request transferred >500 B over the wire.
-    // Then toggle source mode on and off. This causes the CKEditor widget to be
-    // destroyed and then reconstructed. Assert that during this reconstruction,
-    // a second request is sent. This second request should have transferred 0
-    // bytes: the browser should have cached the response, thus resulting in a
-    // much better user experience.
-    $this->assertGreaterThan(500, $this->getLastPreviewRequestTransferSize());
-    $this->pressEditorButton('Source');
-    $this->assertNotEmpty($assert_session->waitForElement('css', '.ck-source-editing-area'));
-    // CKEditor 5 is very smart: if no changes were made in the Source Editing
-    // Area, it will not rerender the contents. In this test, we
-    // want to verify that Media preview responses are cached on the client side
-    // so it is essential that rerendering occurs. To achieve this, we append a
-    // single space.
-    $source_text_area = $this->getSession()->getPage()->find('css', '[name="body[0][value]"] + .ck-editor textarea');
-    $source_text_area->setValue($source_text_area->getValue() . ' ');
-    $this->pressEditorButton('Source');
-    $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
-    $this->assertSame(0, $this->getLastPreviewRequestTransferSize());
+    // Confirm that drupal-media is wrapped by the div.
+    $editor_dom = new \DOMXPath($this->getEditorDataAsDom());
+    $this->assertNotEmpty($editor_dom->query('//div[@data-bar="baz"]/drupal-media'));
   }
 
   /**
@@ -881,300 +557,6 @@ public function testTranslationAlt() {
     $assert_session->elementExists('xpath', '//img[contains(@alt, "' . $qui_est_zartan . '")]');
   }
 
-  /**
-   * Tests linkability of the media CKEditor widget.
-   *
-   * Due to the very different HTML markup generated for the editing view and
-   * the data view, this is explicitly testing the "editingDowncast" and
-   * "dataDowncast" results. These are CKEditor 5 concepts.
-   *
-   * @see https://ckeditor.com/docs/ckeditor5/latest/framework/guides/architecture/editing-engine.html#conversion
-   *
-   * @dataProvider providerLinkability
-   */
-  public function testLinkability(bool $unrestricted) {
-    // Disable filter_html.
-    if ($unrestricted) {
-      FilterFormat::load('test_format')
-        ->setFilterConfig('filter_html', ['status' => FALSE])
-        ->save();
-    }
-
-    $page = $this->getSession()->getPage();
-
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $assert_session = $this->assertSession();
-
-    // Initial state: the Drupal Media CKEditor Widget is not selected.
-    $drupalmedia = $assert_session->waitForElementVisible('css', '.ck-content .ck-widget.drupal-media');
-    $this->assertNotEmpty($drupalmedia);
-    $this->assertFalse($drupalmedia->hasClass('.ck-widget_selected'));
-
-    // Assert the "editingDowncast" HTML before making changes.
-    $assert_session->elementExists('css', '.ck-content .ck-widget.drupal-media > [data-drupal-media-preview]');
-
-    // Assert the "dataDowncast" HTML before making changes.
-    $xpath = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertNotEmpty($xpath->query('//drupal-media'));
-    $this->assertEmpty($xpath->query('//a'));
-
-    // Assert the link button is present and not pressed.
-    $link_button = $this->getEditorButton('Link');
-    $this->assertSame('false', $link_button->getAttribute('aria-pressed'));
-
-    // Wait for the preview to load.
-    $preview = $assert_session->waitForElement('css', '.ck-content .ck-widget.drupal-media [data-drupal-media-preview="ready"]');
-    $this->assertNotEmpty($preview);
-
-    // Tests linking Drupal media.
-    $drupalmedia->click();
-    $this->assertTrue($drupalmedia->hasClass('ck-widget_selected'));
-    $this->assertEditorButtonEnabled('Link');
-    // Assert structure of image toolbar balloon.
-    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
-    $link_media_button = $this->getBalloonButton('Link media');
-    // Click the "Link media" button.
-    $this->assertSame('false', $link_media_button->getAttribute('aria-pressed'));
-    $link_media_button->press();
-    // Assert structure of link form balloon.
-    $balloon = $this->assertVisibleBalloon('.ck-link-form');
-    $url_input = $balloon->find('css', '.ck-labeled-field-view__input-wrapper .ck-input-text');
-    // Fill in link form balloon's <input> and hit "Save".
-    $url_input->setValue('http://linking-embedded-media.com');
-    $balloon->pressButton('Save');
-
-    // Assert the "editingDowncast" HTML after making changes. Assert the link
-    // exists, then assert the link exists. Then assert the expected DOM
-    // structure in detail.
-    $assert_session->elementExists('css', '.ck-content a[href="http://linking-embedded-media.com"]');
-    $assert_session->elementExists('css', '.ck-content .drupal-media.ck-widget > a[href="http://linking-embedded-media.com"] > div[aria-label] > article > div > img[src*="image-test.png"]');
-
-    // Assert the "dataDowncast" HTML after making changes.
-    $xpath = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertNotEmpty($xpath->query('//drupal-media'));
-    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]'));
-    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]/drupal-media'));
-    // Ensure that the media caption is retained and not linked as a result of
-    // linking media.
-    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]/drupal-media[@data-caption="baz"]'));
-
-    // Add `class="trusted"` to the link.
-    $this->assertEmpty($xpath->query('//a[@href="http://linking-embedded-media.com" and @class="trusted"]'));
-    $this->pressEditorButton('Source');
-    $source_text_area = $assert_session->waitForElement('css', '.ck-source-editing-area textarea');
-    $this->assertNotEmpty($source_text_area);
-    $new_value = str_replace('<a ', '<a class="trusted" ', $source_text_area->getValue());
-    $source_text_area->setValue('<p>temp</p>');
-    $source_text_area->setValue($new_value);
-    $this->pressEditorButton('Source');
-
-    // When unrestricted, additional attributes on links should be retained.
-    $xpath = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertCount($unrestricted ? 1 : 0, $xpath->query('//a[@href="http://linking-embedded-media.com" and @class="trusted"]'));
-
-    // Save the entity whose text field is being edited.
-    $page->pressButton('Save');
-
-    // Assert the HTML the end user sees.
-    $assert_session->elementExists('css', $unrestricted
-      ? 'a[href="http://linking-embedded-media.com"].trusted img[src*="image-test.png"]'
-      : 'a[href="http://linking-embedded-media.com"] img[src*="image-test.png"]');
-
-    // Go back to edit the now *linked* <drupal-media>. Everything from this
-    // point onwards is effectively testing "upcasting" and proving there is no
-    // data loss.
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-
-    // Assert the "dataDowncast" HTML before making changes.
-    $xpath = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertNotEmpty($xpath->query('//drupal-media'));
-    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]'));
-    $this->assertNotEmpty($xpath->query('//a[@href="http://linking-embedded-media.com"]/drupal-media'));
-
-    // Tests unlinking media.
-    $drupalmedia->click();
-    $this->assertEditorButtonEnabled('Link');
-    $this->assertSame('true', $this->getEditorButton('Link')->getAttribute('aria-pressed'));
-    // Assert structure of Drupal media toolbar balloon.
-    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
-    $link_media_button = $this->getBalloonButton('Link media');
-    $this->assertSame('true', $link_media_button->getAttribute('aria-pressed'));
-    $link_media_button->click();
-    // Assert structure of link actions balloon.
-    $this->getBalloonButton('Edit link');
-    $unlink_image_button = $this->getBalloonButton('Unlink');
-    // Click the "Unlink" button.
-    $unlink_image_button->click();
-    $this->assertSame('false', $this->getEditorButton('Link')->getAttribute('aria-pressed'));
-
-    // Assert the "editingDowncast" HTML after making changes. Assert the link
-    // exists, then assert no link exists. Then assert the expected DOM
-    // structure in detail.
-    $assert_session->elementNotExists('css', '.ck-content a');
-    $assert_session->elementExists('css', '.ck-content .drupal-media.ck-widget > div[aria-label] > article > div > img[src*="image-test.png"]');
-
-    // Ensure that figcaption exists.
-    // @see https://www.drupal.org/project/drupal/issues/3268318
-    $assert_session->elementExists('css', '.ck-content .drupal-media.ck-widget > figcaption');
-
-    // Assert the "dataDowncast" HTML after making changes.
-    $xpath = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertNotEmpty($xpath->query('//drupal-media'));
-    $this->assertEmpty($xpath->query('//a'));
-  }
-
-  public function providerLinkability(): array {
-    return [
-      'restricted' => [FALSE],
-      'unrestricted' => [TRUE],
-    ];
-  }
-
-  /**
-   * Ensure that manual link decorators work with linkable media.
-   *
-   * @dataProvider providerLinkability
-   */
-  public function testLinkManualDecorator(bool $unrestricted) {
-    \Drupal::service('module_installer')->install(['ckeditor5_manual_decorator_test']);
-    $this->resetAll();
-
-    $decorator = 'Open in a new tab';
-    $decorator_attributes = '[@target="_blank"][@rel="noopener noreferrer"][@class="link-new-tab"]';
-
-    // Disable filter_html.
-    if ($unrestricted) {
-      FilterFormat::load('test_format')
-        ->setFilterConfig('filter_html', ['status' => FALSE])
-        ->save();
-      $decorator = 'Pink color';
-      $decorator_attributes = '[@style="color:pink;"]';
-    }
-
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->assertNotEmpty($drupalmedia = $assert_session->waitForElementVisible('css', '.ck-content .ck-widget.drupal-media'));
-    $drupalmedia->click();
-    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
-
-    // Turn off caption, so we don't accidentally put our link in that text
-    // field instead of on the actual media.
-    $this->getBalloonButton('Toggle caption off')->click();
-    $assert_session->assertNoElementAfterWait('css', 'figure.drupal-media > figcaption');
-
-    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
-    $this->getBalloonButton('Link media')->click();
-
-    $balloon = $this->assertVisibleBalloon('.ck-link-form');
-    $url_input = $balloon->find('css', '.ck-labeled-field-view__input-wrapper .ck-input-text');
-    $url_input->setValue('http://linking-embedded-media.com');
-    $this->getBalloonButton($decorator)->click();
-    $balloon->pressButton('Save');
-
-    $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.drupal-media a'));
-    $this->assertVisibleBalloon('.ck-link-actions');
-
-    $xpath = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertNotEmpty($xpath->query("//a[@href='http://linking-embedded-media.com']$decorator_attributes"));
-    $this->assertNotEmpty($xpath->query("//a[@href='http://linking-embedded-media.com']$decorator_attributes/drupal-media"));
-
-    // Ensure that manual decorators upcast correctly.
-    $page->pressButton('Save');
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->assertNotEmpty($drupalmedia = $assert_session->waitForElementVisible('css', '.ck-content .ck-widget.drupal-media'));
-    $xpath = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertNotEmpty($xpath->query("//a[@href='http://linking-embedded-media.com']$decorator_attributes"));
-    $this->assertNotEmpty($xpath->query("//a[@href='http://linking-embedded-media.com']$decorator_attributes/drupal-media"));
-
-    // Finally, ensure that media can be unlinked.
-    $drupalmedia->click();
-    $this->assertVisibleBalloon('.ck-toolbar[aria-label="Drupal Media toolbar"]');
-    $this->getBalloonButton('Link media')->click();
-    $this->assertVisibleBalloon('.ck-link-actions');
-    $this->getBalloonButton('Unlink')->click();
-
-    $this->assertTrue($assert_session->waitForElementRemoved('css', '.drupal-media a'));
-    $xpath = new \DOMXPath($this->getEditorDataAsDom());
-    $this->assertEmpty($xpath->query('//a'));
-    $this->assertNotEmpty($xpath->query('//drupal-media'));
-  }
-
-  /**
-   * Tests preview route access.
-   *
-   * @param bool $media_embed_enabled
-   *   Whether to test with media_embed filter enabled on the text format.
-   * @param bool $can_use_format
-   *   Whether the logged in user is allowed to use the text format.
-   *
-   * @dataProvider previewAccessProvider
-   */
-  public function testEmbedPreviewAccess($media_embed_enabled, $can_use_format) {
-    // Reconfigure the host entity's text format to suit our needs.
-    /** @var \Drupal\filter\FilterFormatInterface $format */
-    $format = FilterFormat::load($this->host->body->format);
-    $format->set('filters', [
-      'filter_align' => ['status' => TRUE],
-      'filter_caption' => ['status' => TRUE],
-      'media_embed' => ['status' => $media_embed_enabled],
-    ]);
-    $format->save();
-
-    $permissions = [
-      'bypass node access',
-    ];
-    if ($can_use_format) {
-      $permissions[] = $format->getPermissionName();
-    }
-    $this->drupalLogin($this->drupalCreateUser($permissions));
-    $this->drupalGet($this->host->toUrl('edit-form'));
-
-    $assert_session = $this->assertSession();
-    if ($can_use_format) {
-      $this->waitForEditor();
-      if ($media_embed_enabled) {
-        // The preview rendering, which in this test will use Starterkit theme's
-        // media.html.twig template, will fail without the CSRF token/header.
-        // @see ::testEmbeddedMediaPreviewWithCsrfToken()
-        $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'article.media'));
-      }
-      else {
-        // If the filter isn't enabled, there won't be an error, but the
-        // preview shouldn't be rendered.
-        $assert_session->assertWaitOnAjaxRequest();
-        $assert_session->elementNotExists('css', 'article.media');
-      }
-    }
-    else {
-      $assert_session->pageTextContains('This field has been disabled because you do not have sufficient permissions to edit it.');
-    }
-  }
-
-  /**
-   * Data provider for ::testEmbedPreviewAccess.
-   */
-  public function previewAccessProvider() {
-    return [
-      'media_embed filter enabled' => [
-        TRUE,
-        TRUE,
-      ],
-      'media_embed filter disabled' => [
-        FALSE,
-        TRUE,
-      ],
-      'media_embed filter enabled, user not allowed to use text format' => [
-        TRUE,
-        FALSE,
-      ],
-    ];
-  }
-
   /**
    * Tests alignment integration.
    *
@@ -1627,71 +1009,4 @@ public function providerTestViewMode(): array {
     ];
   }
 
-  /**
-   * Verifies value of an attribute on the downcast <drupal-media> element.
-   *
-   * Assumes CKEditor is in source mode.
-   *
-   * @param string $attribute
-   *   The attribute to check.
-   * @param string|null $value
-   *   Either a string value or if NULL, asserts that <drupal-media> element
-   *   doesn't have the attribute.
-   *
-   * @internal
-   */
-  protected function assertSourceAttributeSame(string $attribute, ?string $value): void {
-    $dom = $this->getEditorDataAsDom();
-    $drupal_media = (new \DOMXPath($dom))->query('//drupal-media');
-    $this->assertNotEmpty($drupal_media);
-    if ($value === NULL) {
-      $this->assertFalse($drupal_media[0]->hasAttribute($attribute));
-    }
-    else {
-      $this->assertSame($value, $drupal_media[0]->getAttribute($attribute));
-    }
-  }
-
-  /**
-   * Gets the transfer size of the last preview request.
-   *
-   * @return int
-   *   The size of the bytes transferred.
-   */
-  protected function getLastPreviewRequestTransferSize() {
-    $javascript = <<<JS
-(function(){
-  return window.performance
-    .getEntries()
-    .filter(function (entry) {
-      return entry.initiatorType == 'fetch' && entry.name.indexOf('/media/test_format/preview') !== -1;
-    })
-    .pop()
-    .transferSize;
-})()
-JS;
-    return $this->getSession()->evaluateScript($javascript);
-  }
-
-  /**
-   * Ensure media preview isn't clickable.
-   */
-  public function testMediaPointerEvent() {
-    $entityViewDisplay = EntityViewDisplay::load('media.image.view_mode_1');
-    $thumbnail = $entityViewDisplay->getComponent('thumbnail');
-    $thumbnail['settings']['image_link'] = 'file';
-    $entityViewDisplay->setComponent('thumbnail', $thumbnail);
-    $entityViewDisplay->save();
-
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-    $url = $this->host->toUrl('edit-form');
-    $this->drupalGet($url);
-    $this->waitForEditor();
-    $assert_session->waitForLink('default alt');
-    $page->find('css', '.ck .drupal-media')->click();
-    // Assert that the media preview is not clickable by comparing the URL.
-    $this->assertEquals($url->toString(), $this->getUrl());
-  }
-
 }
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTestBase.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..caeff363ee196b20f904258e270ce26cd316b2e8
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/MediaTestBase.php
@@ -0,0 +1,271 @@
+<?php
+
+namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
+
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\Entity\Entity\EntityViewMode;
+use Drupal\editor\Entity\Editor;
+use Drupal\file\Entity\File;
+use Drupal\filter\Entity\FilterFormat;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\media\Entity\Media;
+use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
+use Drupal\Tests\TestFileCreationTrait;
+use Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
+use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
+use Symfony\Component\Validator\ConstraintViolation;
+
+/**
+ * Base class for CKEditor 5 Media integration tests.
+ *
+ * @internal
+ */
+abstract class MediaTestBase extends WebDriverTestBase {
+
+  use CKEditor5TestTrait;
+  use MediaTypeCreationTrait;
+  use TestFileCreationTrait;
+
+  /**
+   * The user to use during testing.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * The sample Media entity to embed.
+   *
+   * @var \Drupal\media\MediaInterface
+   */
+  protected $media;
+
+  /**
+   * The second sample Media entity to embed used in one of the tests.
+   *
+   * @var \Drupal\media\MediaInterface
+   */
+  protected $mediaFile;
+
+  /**
+   * A host entity with a body field to embed media in.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $host;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'ckeditor5',
+    'media',
+    'node',
+    'text',
+    'media_test_embed',
+    'media_library',
+    'ckeditor5_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'starterkit_theme';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    EntityViewMode::create([
+      'id' => 'media.view_mode_1',
+      'targetEntityType' => 'media',
+      'status' => TRUE,
+      'enabled' => TRUE,
+      'label' => 'View Mode 1',
+    ])->save();
+    EntityViewMode::create([
+      'id' => 'media.22222',
+      'targetEntityType' => 'media',
+      'status' => TRUE,
+      'enabled' => TRUE,
+      'label' => 'View Mode 2 has Numeric ID',
+    ])->save();
+    FilterFormat::create([
+      'format' => 'test_format',
+      'name' => 'Test format',
+      'filters' => [
+        'filter_html' => [
+          'status' => TRUE,
+          'settings' => [
+            'allowed_html' => '<p> <br> <strong> <em> <a href> <drupal-media data-entity-type data-entity-uuid data-align data-view-mode data-caption alt>',
+          ],
+        ],
+        'filter_align' => ['status' => TRUE],
+        'filter_caption' => ['status' => TRUE],
+        'media_embed' => [
+          'status' => TRUE,
+          'settings' => [
+            'default_view_mode' => 'view_mode_1',
+            'allowed_view_modes' => [
+              'view_mode_1' => 'view_mode_1',
+              '22222' => '22222',
+            ],
+            'allowed_media_types' => [],
+          ],
+        ],
+      ],
+    ])->save();
+    Editor::create([
+      'editor' => 'ckeditor5',
+      'format' => 'test_format',
+      'settings' => [
+        'toolbar' => [
+          'items' => [
+            'sourceEditing',
+            'link',
+            'bold',
+            'italic',
+          ],
+        ],
+        'plugins' => [
+          'ckeditor5_sourceEditing' => [
+            'allowed_tags' => [],
+          ],
+          'media_media' => [
+            'allow_view_mode_override' => TRUE,
+          ],
+        ],
+      ],
+      'image_upload' => [
+        'status' => FALSE,
+      ],
+    ])->save();
+    $this->assertSame([], array_map(
+      function (ConstraintViolation $v) {
+        return (string) $v->getMessage();
+      },
+      iterator_to_array(CKEditor5::validatePair(
+        Editor::load('test_format'),
+        FilterFormat::load('test_format')
+      ))
+    ));
+
+    // Note that media_install() grants 'view media' to all users by default.
+    $this->adminUser = $this->drupalCreateUser([
+      'use text format test_format',
+      'bypass node access',
+    ]);
+
+    // Create a sample media entity to be embedded.
+    $this->createMediaType('image', ['id' => 'image']);
+    File::create([
+      'uri' => $this->getTestFiles('image')[0]->uri,
+    ])->save();
+    $this->media = Media::create([
+      'bundle' => 'image',
+      'name' => 'Screaming hairy armadillo',
+      'field_media_image' => [
+        [
+          'target_id' => 1,
+          'alt' => 'default alt',
+          'title' => 'default title',
+        ],
+      ],
+    ]);
+    $this->media->save();
+
+    $this->createMediaType('file', ['id' => 'file']);
+    File::create([
+      'uri' => $this->getTestFiles('text')[0]->uri,
+    ])->save();
+    $this->mediaFile = Media::create([
+      'bundle' => 'file',
+      'name' => 'Information about screaming hairy armadillo',
+      'field_media_file' => [
+        [
+          'target_id' => 2,
+        ],
+      ],
+    ]);
+    $this->mediaFile->save();
+
+    // Set created media types for each view mode.
+    EntityViewDisplay::create([
+      'id' => 'media.image.view_mode_1',
+      'targetEntityType' => 'media',
+      'status' => TRUE,
+      'bundle' => 'image',
+      'mode' => 'view_mode_1',
+    ])->save();
+    EntityViewDisplay::create([
+      'id' => 'media.image.22222',
+      'targetEntityType' => 'media',
+      'status' => TRUE,
+      'bundle' => 'image',
+      'mode' => '22222',
+    ])->save();
+
+    // Create a sample host entity to embed media in.
+    $this->drupalCreateContentType(['type' => 'blog']);
+    $this->host = $this->createNode([
+      'type' => 'blog',
+      'title' => 'Animals with strange names',
+      'body' => [
+        'value' => '<drupal-media data-entity-type="media" data-entity-uuid="' . $this->media->uuid() . '" data-caption="baz"></drupal-media>',
+        'format' => 'test_format',
+      ],
+    ]);
+    $this->host->save();
+
+    $this->drupalLogin($this->adminUser);
+  }
+
+  /**
+   * Verifies value of an attribute on the downcast <drupal-media> element.
+   *
+   * Assumes CKEditor is in source mode.
+   *
+   * @param string $attribute
+   *   The attribute to check.
+   * @param string|null $value
+   *   Either a string value or if NULL, asserts that <drupal-media> element
+   *   doesn't have the attribute.
+   *
+   * @internal
+   */
+  protected function assertSourceAttributeSame(string $attribute, ?string $value): void {
+    $dom = $this->getEditorDataAsDom();
+    $drupal_media = (new \DOMXPath($dom))->query('//drupal-media');
+    $this->assertNotEmpty($drupal_media);
+    if ($value === NULL) {
+      $this->assertFalse($drupal_media[0]->hasAttribute($attribute));
+    }
+    else {
+      $this->assertSame($value, $drupal_media[0]->getAttribute($attribute));
+    }
+  }
+
+  /**
+   * Gets the transfer size of the last preview request.
+   *
+   * @return int
+   *   The size of the bytes transferred.
+   */
+  protected function getLastPreviewRequestTransferSize() {
+    $javascript = <<<JS
+(function(){
+  return window.performance
+    .getEntries()
+    .filter(function (entry) {
+      return entry.initiatorType == 'fetch' && entry.name.indexOf('/media/test_format/preview') !== -1;
+    })
+    .pop()
+    .transferSize;
+})()
+JS;
+    return $this->getSession()->evaluateScript($javascript);
+  }
+
+}
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingEmptyElementTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingEmptyElementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..eddd914fb75c64710f5cc826bb0d235da8995a89
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingEmptyElementTest.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
+
+use Drupal\ckeditor5\HTMLRestrictions;
+use Drupal\editor\Entity\Editor;
+use Drupal\filter\Entity\FilterFormat;
+use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
+use Symfony\Component\Validator\ConstraintViolation;
+
+// cspell:ignore gramma sourceediting
+
+/**
+ * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\SourceEditing
+ * @covers \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getCKEditor5PluginConfig
+ * @group ckeditor5
+ * @group #slow
+ * @internal
+ */
+class SourceEditingEmptyElementTest extends SourceEditingTestBase {
+
+  /**
+   * Tests creating empty inline elements using Source Editing.
+   *
+   * @testWith ["<p>Before <i class=\"fab fa-drupal\"></i> and after.</p>", "<p>Before and after.</p>", "<p>Before and after.</p>", null]
+   *           ["<p>Before <i class=\"fab fa-drupal\"></i> and after.</p>", "<p>Before &nbsp;and after.</p>", null, "<i>"]
+   *           ["<p>Before <i class=\"fab fa-drupal\"></i> and after.</p>", null, null, "<i class>"]
+   *           ["<p>Before <span class=\"icon my-icon\"></span> and after.</p>", "<p>Before and after.</p>", "<p>Before and after.</p>", null]
+   *           ["<p>Before <span class=\"icon my-icon\"></span> and after.</p>", "<p>Before &nbsp;and after.</p>", null, "<span>"]
+   *           ["<p>Before <span class=\"icon my-icon\"></span> and after.</p>", "<p>Before <span class=\"icon\"></span> and after.</p>", null, "<span class=\"icon\">"]
+   */
+  public function testEmptyInlineElement(string $input, ?string $expected_output_when_restricted, ?string $expected_output_when_unrestricted, ?string $allowed_elements_string): void {
+    $this->host->body->value = $input;
+    $this->host->save();
+
+    // If no expected output is specified, it should be identical to the input.
+    if ($expected_output_when_restricted === NULL) {
+      $expected_output_when_restricted = $input;
+    }
+    if ($expected_output_when_unrestricted === NULL) {
+      $expected_output_when_unrestricted = $input;
+    }
+
+    $text_editor = Editor::load('test_format');
+    $text_format = FilterFormat::load('test_format');
+    if ($allowed_elements_string) {
+      // Allow creating additional HTML using SourceEditing.
+      $settings = $text_editor->getSettings();
+      $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'][] = $allowed_elements_string;
+      $text_editor->setSettings($settings);
+
+      // Keep the allowed HTML tags in sync.
+      $allowed_elements = HTMLRestrictions::fromTextFormat($text_format);
+      $updated_allowed_tags = $allowed_elements->merge(HTMLRestrictions::fromString($allowed_elements_string));
+      $filter_html_config = $text_format->filters('filter_html')
+        ->getConfiguration();
+      $filter_html_config['settings']['allowed_html'] = $updated_allowed_tags->toFilterHtmlAllowedTagsString();
+      $text_format->setFilterConfig('filter_html', $filter_html_config);
+
+      // Verify the text format and editor are still a valid pair.
+      $this->assertSame([], array_map(
+        function (ConstraintViolation $v) {
+          return (string) $v->getMessage();
+        },
+        iterator_to_array(CKEditor5::validatePair(
+          $text_editor,
+          $text_format
+        ))
+      ));
+
+      // If valid, save both.
+      $text_format->save();
+      $text_editor->save();
+    }
+
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $this->assertSame($expected_output_when_restricted, $this->getEditorDataAsHtmlString());
+
+    // Make the text format unrestricted: disable filter_html.
+    $text_format
+      ->setFilterConfig('filter_html', ['status' => FALSE])
+      ->save();
+
+    // Verify the text format and editor are still a valid pair.
+    $this->assertSame([], array_map(
+      function (ConstraintViolation $v) {
+        return (string) $v->getMessage();
+      },
+      iterator_to_array(CKEditor5::validatePair(
+        $text_editor,
+        $text_format
+      ))
+    ));
+
+    // Test with a text format allowing arbitrary HTML.
+    $this->drupalGet($this->host->toUrl('edit-form'));
+    $this->waitForEditor();
+    $this->assertSame($expected_output_when_unrestricted, $this->getEditorDataAsHtmlString());
+  }
+
+}
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingTest.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingTest.php
index 14f47c9a0c3e10e2ee448b30db15cbafd8808faa..ddecf81780b477489a2666544dff18fd91fe83da 100644
--- a/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingTest.php
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingTest.php
@@ -5,7 +5,6 @@
 use Drupal\ckeditor5\HTMLRestrictions;
 use Drupal\editor\Entity\Editor;
 use Drupal\filter\Entity\FilterFormat;
-use Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
 use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
 use Symfony\Component\Validator\ConstraintViolation;
 
@@ -15,113 +14,10 @@
  * @coversDefaultClass \Drupal\ckeditor5\Plugin\CKEditor5Plugin\SourceEditing
  * @covers \Drupal\ckeditor5\Plugin\CKEditor5PluginManager::getCKEditor5PluginConfig
  * @group ckeditor5
+ * @group #slow
  * @internal
  */
-class SourceEditingTest extends CKEditor5TestBase {
-
-  use CKEditor5TestTrait;
-
-  /**
-   * The user to use during testing.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $adminUser;
-
-  /**
-   * A host entity with a body field whose text to edit with CKEditor 5.
-   *
-   * @var \Drupal\node\NodeInterface
-   */
-  protected $host;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = [
-    'ckeditor5',
-    'node',
-    'text',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $defaultTheme = 'stark';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    FilterFormat::create([
-      'format' => 'test_format',
-      'name' => 'Test format',
-      'filters' => [
-        'filter_html' => [
-          'status' => TRUE,
-          'settings' => [
-            'allowed_html' => '<div class> <p> <br> <a href> <ol> <ul> <li>',
-          ],
-        ],
-        'filter_align' => ['status' => TRUE],
-        'filter_caption' => ['status' => TRUE],
-      ],
-    ])->save();
-    Editor::create([
-      'editor' => 'ckeditor5',
-      'format' => 'test_format',
-      'settings' => [
-        'toolbar' => [
-          'items' => [
-            'sourceEditing',
-            'link',
-            'bulletedList',
-            'numberedList',
-          ],
-        ],
-        'plugins' => [
-          'ckeditor5_sourceEditing' => [
-            'allowed_tags' => ['<div class>'],
-          ],
-          'ckeditor5_list' => [
-            'reversed' => FALSE,
-            'startIndex' => FALSE,
-          ],
-        ],
-      ],
-      'image_upload' => [
-        'status' => FALSE,
-      ],
-    ])->save();
-    $this->assertSame([], array_map(
-      function (ConstraintViolation $v) {
-        return (string) $v->getMessage();
-      },
-      iterator_to_array(CKEditor5::validatePair(
-        Editor::load('test_format'),
-        FilterFormat::load('test_format')
-      ))
-    ));
-    $this->adminUser = $this->drupalCreateUser([
-      'use text format test_format',
-      'bypass node access',
-    ]);
-
-    // Create a sample host entity to test CKEditor 5.
-    $this->host = $this->createNode([
-      'type' => 'page',
-      'title' => 'Animals with strange names',
-      'body' => [
-        'value' => '',
-        'format' => 'test_format',
-      ],
-    ]);
-    $this->host->save();
-
-    $this->drupalLogin($this->adminUser);
-  }
+class SourceEditingTest extends SourceEditingTestBase {
 
   /**
    * @covers \Drupal\ckeditor5\Plugin\CKEditor5Plugin\SourceEditing::buildConfigurationForm
@@ -355,84 +251,4 @@ public function providerAllowingExtraAttributes(): array {
     ];
   }
 
-  /**
-   * Tests creating empty inline elements using Source Editing.
-   *
-   * @testWith ["<p>Before <i class=\"fab fa-drupal\"></i> and after.</p>", "<p>Before and after.</p>", "<p>Before and after.</p>", null]
-   *           ["<p>Before <i class=\"fab fa-drupal\"></i> and after.</p>", "<p>Before &nbsp;and after.</p>", null, "<i>"]
-   *           ["<p>Before <i class=\"fab fa-drupal\"></i> and after.</p>", null, null, "<i class>"]
-   *           ["<p>Before <span class=\"icon my-icon\"></span> and after.</p>", "<p>Before and after.</p>", "<p>Before and after.</p>", null]
-   *           ["<p>Before <span class=\"icon my-icon\"></span> and after.</p>", "<p>Before &nbsp;and after.</p>", null, "<span>"]
-   *           ["<p>Before <span class=\"icon my-icon\"></span> and after.</p>", "<p>Before <span class=\"icon\"></span> and after.</p>", null, "<span class=\"icon\">"]
-   */
-  public function testEmptyInlineElement(string $input, ?string $expected_output_when_restricted, ?string $expected_output_when_unrestricted, ?string $allowed_elements_string): void {
-    $this->host->body->value = $input;
-    $this->host->save();
-
-    // If no expected output is specified, it should be identical to the input.
-    if ($expected_output_when_restricted === NULL) {
-      $expected_output_when_restricted = $input;
-    }
-    if ($expected_output_when_unrestricted === NULL) {
-      $expected_output_when_unrestricted = $input;
-    }
-
-    $text_editor = Editor::load('test_format');
-    $text_format = FilterFormat::load('test_format');
-    if ($allowed_elements_string) {
-      // Allow creating additional HTML using SourceEditing.
-      $settings = $text_editor->getSettings();
-      $settings['plugins']['ckeditor5_sourceEditing']['allowed_tags'][] = $allowed_elements_string;
-      $text_editor->setSettings($settings);
-
-      // Keep the allowed HTML tags in sync.
-      $allowed_elements = HTMLRestrictions::fromTextFormat($text_format);
-      $updated_allowed_tags = $allowed_elements->merge(HTMLRestrictions::fromString($allowed_elements_string));
-      $filter_html_config = $text_format->filters('filter_html')
-        ->getConfiguration();
-      $filter_html_config['settings']['allowed_html'] = $updated_allowed_tags->toFilterHtmlAllowedTagsString();
-      $text_format->setFilterConfig('filter_html', $filter_html_config);
-
-      // Verify the text format and editor are still a valid pair.
-      $this->assertSame([], array_map(
-        function (ConstraintViolation $v) {
-          return (string) $v->getMessage();
-        },
-        iterator_to_array(CKEditor5::validatePair(
-          $text_editor,
-          $text_format
-        ))
-      ));
-
-      // If valid, save both.
-      $text_format->save();
-      $text_editor->save();
-    }
-
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $this->assertSame($expected_output_when_restricted, $this->getEditorDataAsHtmlString());
-
-    // Make the text format unrestricted: disable filter_html.
-    $text_format
-      ->setFilterConfig('filter_html', ['status' => FALSE])
-      ->save();
-
-    // Verify the text format and editor are still a valid pair.
-    $this->assertSame([], array_map(
-      function (ConstraintViolation $v) {
-        return (string) $v->getMessage();
-      },
-      iterator_to_array(CKEditor5::validatePair(
-        $text_editor,
-        $text_format
-      ))
-    ));
-
-    // Test with a text format allowing arbitrary HTML.
-    $this->drupalGet($this->host->toUrl('edit-form'));
-    $this->waitForEditor();
-    $this->assertSame($expected_output_when_unrestricted, $this->getEditorDataAsHtmlString());
-  }
-
 }
diff --git a/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingTestBase.php b/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..d5b916323ba1709364d1a131396e6119f5b05b30
--- /dev/null
+++ b/core/modules/ckeditor5/tests/src/FunctionalJavascript/SourceEditingTestBase.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
+
+use Drupal\editor\Entity\Editor;
+use Drupal\filter\Entity\FilterFormat;
+use Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
+use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
+use Symfony\Component\Validator\ConstraintViolation;
+
+// cspell:ignore gramma sourceediting
+
+/**
+ * @internal
+ */
+abstract class SourceEditingTestBase extends CKEditor5TestBase {
+
+  use CKEditor5TestTrait;
+
+  /**
+   * The user to use during testing.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * A host entity with a body field whose text to edit with CKEditor 5.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $host;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'ckeditor5',
+    'node',
+    'text',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    FilterFormat::create([
+      'format' => 'test_format',
+      'name' => 'Test format',
+      'filters' => [
+        'filter_html' => [
+          'status' => TRUE,
+          'settings' => [
+            'allowed_html' => '<div class> <p> <br> <a href> <ol> <ul> <li>',
+          ],
+        ],
+        'filter_align' => ['status' => TRUE],
+        'filter_caption' => ['status' => TRUE],
+      ],
+    ])->save();
+    Editor::create([
+      'editor' => 'ckeditor5',
+      'format' => 'test_format',
+      'settings' => [
+        'toolbar' => [
+          'items' => [
+            'sourceEditing',
+            'link',
+            'bulletedList',
+            'numberedList',
+          ],
+        ],
+        'plugins' => [
+          'ckeditor5_sourceEditing' => [
+            'allowed_tags' => ['<div class>'],
+          ],
+          'ckeditor5_list' => [
+            'reversed' => FALSE,
+            'startIndex' => FALSE,
+          ],
+        ],
+      ],
+      'image_upload' => [
+        'status' => FALSE,
+      ],
+    ])->save();
+    $this->assertSame([], array_map(
+      function (ConstraintViolation $v) {
+        return (string) $v->getMessage();
+      },
+      iterator_to_array(CKEditor5::validatePair(
+        Editor::load('test_format'),
+        FilterFormat::load('test_format')
+      ))
+    ));
+    $this->adminUser = $this->drupalCreateUser([
+      'use text format test_format',
+      'bypass node access',
+    ]);
+
+    // Create a sample host entity to test CKEditor 5.
+    $this->host = $this->createNode([
+      'type' => 'page',
+      'title' => 'Animals with strange names',
+      'body' => [
+        'value' => '',
+        'format' => 'test_format',
+      ],
+    ]);
+    $this->host->save();
+
+    $this->drupalLogin($this->adminUser);
+  }
+
+}
diff --git a/core/modules/ckeditor5/tests/src/Kernel/CKEditor5ValidationTestTrait.php b/core/modules/ckeditor5/tests/src/Kernel/CKEditor5ValidationTestTrait.php
index 16c43e5ba0ec5e0d51f846b27be2747b8c908075..fc868d08e4aca0d342b1b6d44fd7c68d2824e5c3 100644
--- a/core/modules/ckeditor5/tests/src/Kernel/CKEditor5ValidationTestTrait.php
+++ b/core/modules/ckeditor5/tests/src/Kernel/CKEditor5ValidationTestTrait.php
@@ -7,6 +7,7 @@
 use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
 use Drupal\editor\EditorInterface;
 use Drupal\filter\FilterFormatInterface;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
 
 /**
  * Defines a trait for testing CKEditor 5 validity.
@@ -30,6 +31,19 @@ trait CKEditor5ValidationTestTrait {
    */
   private function validatePairToViolationsArray(EditorInterface $text_editor, FilterFormatInterface $text_format, bool $all_compatibility_problems): array {
     $violations = CKEditor5::validatePair($text_editor, $text_format, $all_compatibility_problems);
+    return self::violationsToArray($violations);
+  }
+
+  /**
+   * Transforms a constraint violation list object to an assertable array.
+   *
+   * @param \Symfony\Component\Validator\ConstraintViolationListInterface $violations
+   *   Validation constraint violations.
+   *
+   * @return array
+   *   An array with property paths as keys and violation messages as values.
+   */
+  private static function violationsToArray(ConstraintViolationListInterface $violations): array {
     $actual_violations = [];
     foreach ($violations as $violation) {
       if (!isset($actual_violations[$violation->getPropertyPath()])) {
diff --git a/core/modules/ckeditor5/tests/src/Kernel/ValidatorsTest.php b/core/modules/ckeditor5/tests/src/Kernel/ValidatorsTest.php
index 454826a67f2ba2df6be6d7b65a9a4a2e9deef5ea..376e200c827799dd3e6966d54a75e0b1b36447a2 100644
--- a/core/modules/ckeditor5/tests/src/Kernel/ValidatorsTest.php
+++ b/core/modules/ckeditor5/tests/src/Kernel/ValidatorsTest.php
@@ -95,11 +95,8 @@ public function test(array $ckeditor5_settings, array $expected_violations) {
     );
     $violations = $typed_config->validate();
 
-    $actual_violations = [];
-    foreach ($violations as $violation) {
-      $actual_violations[$violation->getPropertyPath()] = (string) $violation->getMessage();
-    }
-    $this->assertSame($expected_violations, $actual_violations);
+    $actual_violations = self::violationsToArray($violations);
+    $this->assertSame($expected_violations, self::violationsToArray($violations));
 
     if (empty($expected_violations)) {
       $this->assertConfigSchema(
@@ -509,7 +506,10 @@ public function provider(): array {
       ],
       'violations' => [
         'settings.plugins.ckeditor5_style.styles.0.element' => 'The following tag is missing the required attribute <code>class</code>: <code>&lt;p&gt;</code>.',
-        'settings.plugins.ckeditor5_style.styles.1.element' => 'The following tag does not have the minimum of 1 allowed values for the required attribute <code>class</code>: <code>&lt;blockquote class=&quot;&quot;&gt;</code>.',
+        'settings.plugins.ckeditor5_style.styles.1.element' => [
+          'The following tag is not valid HTML: <em class="placeholder">&lt;blockquote class=&quot;&quot;&gt;</em>.',
+          'The following tag does not have the minimum of 1 allowed values for the required attribute <code>class</code>: <code>&lt;blockquote class=&quot;&quot;&gt;</code>.',
+        ],
       ],
     ];
     $data['VALID: Style plugin has multiple styles with different labels'] = [
diff --git a/core/modules/ckeditor5/tests/src/Kernel/WildcardHtmlSupportTest.php b/core/modules/ckeditor5/tests/src/Kernel/WildcardHtmlSupportTest.php
index a5e0787f80c6d55944f00ddc977ac2d2e2c1b65b..9a3023738a7caa98adf5725a849d5108980c2ffc 100644
--- a/core/modules/ckeditor5/tests/src/Kernel/WildcardHtmlSupportTest.php
+++ b/core/modules/ckeditor5/tests/src/Kernel/WildcardHtmlSupportTest.php
@@ -159,7 +159,7 @@ public function providerGhsConfiguration(): array {
         ['alignment'],
       ],
       '<$text-container> with attribute from multiple plugins' => [
-        '<p data-llama class"> <br>',
+        '<p data-llama class> <br>',
         ['<$text-container data-llama>', '<p class>'],
         [
           [
diff --git a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5EditorHeightTest.js b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5EditorHeightTest.js
index 2d1f001d2e953d2fcd13333eb1626a1216738b9b..b5bf5f41ecc20ce32427cba795f5bfd42dc9c065 100644
--- a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5EditorHeightTest.js
+++ b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5EditorHeightTest.js
@@ -1,7 +1,11 @@
 module.exports = {
   '@tags': ['core', 'ckeditor5'],
   before(browser) {
-    browser.drupalInstall({ installProfile: 'minimal' });
+    browser
+      .drupalInstall({ installProfile: 'minimal' })
+      .drupalInstallModule('ckeditor5', true)
+      .drupalInstallModule('field_ui');
+
     // Set fixed (desktop-ish) size to ensure a maximum viewport.
     browser.resizeWindow(1920, 1080);
   },
@@ -11,17 +15,6 @@ module.exports = {
   'Ensure CKEditor respects field widget row value': (browser) => {
     browser.drupalLoginAsAdmin(() => {
       browser
-        // Enable required modules.
-        .drupalRelativeURL('/admin/modules')
-        .click('[name="modules[ckeditor5][enable]"]')
-        .click('[name="modules[field_ui][enable]"]')
-        .submitForm('input[type="submit"]') // Submit module form.
-        .waitForElementVisible(
-          '.system-modules-confirm-form input[value="Continue"]',
-        )
-        .submitForm('input[value="Continue"]') // Confirm installation of dependencies.
-        .waitForElementVisible('.system-modules', 10000)
-
         // Create new input format.
         .drupalRelativeURL('/admin/config/content/formats/add')
         .waitForElementVisible('[data-drupal-selector="edit-name"]')
diff --git a/core/modules/ckeditor5/tests/src/Unit/LanguagePluginTest.php b/core/modules/ckeditor5/tests/src/Unit/LanguagePluginTest.php
index 395004ecec5d21489279fb10c65c5ff50ef1132d..de73ca706f09bba588a09158546c977652d5862d 100644
--- a/core/modules/ckeditor5/tests/src/Unit/LanguagePluginTest.php
+++ b/core/modules/ckeditor5/tests/src/Unit/LanguagePluginTest.php
@@ -5,7 +5,11 @@
 namespace Drupal\Tests\ckeditor5\Unit;
 
 use Drupal\ckeditor5\Plugin\CKEditor5Plugin\Language;
+use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
+use Drupal\Core\Language\Language as LanguageLanguage;
 use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
 use Drupal\editor\EditorInterface;
 use Drupal\Tests\UnitTestCase;
 
@@ -20,7 +24,7 @@ class LanguagePluginTest extends UnitTestCase {
    * Provides a list of configs to test.
    */
   public static function providerGetDynamicPluginConfig(): array {
-    $un_expected_output = [
+    $united_nations_expected_output = [
       'language' => [
         'textPartLanguage' => [
           [
@@ -54,7 +58,25 @@ public static function providerGetDynamicPluginConfig(): array {
     return [
       'un' => [
         ['language_list' => 'un'],
-        $un_expected_output,
+        $united_nations_expected_output,
+      ],
+      'site_configured' => [
+        ['language_list' => 'site_configured'],
+        [
+          'language' => [
+            'textPartLanguage' => [
+              [
+                'title' => 'Arabic',
+                'languageCode' => 'ar',
+                'textDirection' => 'rtl',
+              ],
+              [
+                'title' => 'German',
+                'languageCode' => 'de',
+              ],
+            ],
+          ],
+        ],
       ],
       'all' => [
         ['language_list' => 'all'],
@@ -66,7 +88,7 @@ public static function providerGetDynamicPluginConfig(): array {
       ],
       'default configuration' => [
         [],
-        $un_expected_output,
+        $united_nations_expected_output,
       ],
     ];
   }
@@ -101,7 +123,20 @@ protected static function buildExpectedDynamicConfig(array $language_list) {
    * @dataProvider providerGetDynamicPluginConfig
    */
   public function testGetDynamicPluginConfig(array $configuration, array $expected_dynamic_config): void {
-    $plugin = new Language($configuration, 'ckeditor5_language', NULL);
+    $route_provider = $this->prophesize(RouteProviderInterface::class);
+    $language_manager = $this->prophesize(LanguageManagerInterface::class);
+    $language_manager->getLanguages()->willReturn([
+      new LanguageLanguage([
+        'id' => 'de',
+        'name' => 'German',
+      ]),
+      new LanguageLanguage([
+        'id' => 'ar',
+        'name' => 'Arabic',
+        'direction' => 'rtl',
+      ]),
+    ]);
+    $plugin = new Language($configuration, 'ckeditor5_language', new CKEditor5PluginDefinition(['id' => 'IRRELEVANT-FOR-A-UNIT-TEST']), $language_manager->reveal(), $route_provider->reveal());
     $dynamic_config = $plugin->getDynamicPluginConfig([], $this->prophesize(EditorInterface::class)
       ->reveal());
     $this->assertSame($expected_dynamic_config, $dynamic_config);
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 8caa332efca4ee19154d840259c7caedbc5ad5c6..a8781c599024ab068e8de4f61efca5128cbd1787 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -16,6 +16,7 @@
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\Core\Entity\Entity\EntityViewMode;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
@@ -785,8 +786,10 @@ function comment_entity_view_display_presave(EntityViewDisplayInterface $display
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function comment_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'comment/drupal.comment-icon';
+function comment_field_type_category_info_alter(&$definitions) {
+  // The `comment` field type belongs in the `general` category, so the
+  // libraries need to be attached using an alter hook.
+  $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'comment/drupal.comment-icon';
 }
diff --git a/core/modules/comment/css/comment.icon.theme.css b/core/modules/comment/css/comment.icon.theme.css
index bc82aa674a081a72b91b6ce4a378b6b68199dee8..bbeb16aabe5083ddbbf9511e9f34a12ea9caa140 100644
--- a/core/modules/comment/css/comment.icon.theme.css
+++ b/core/modules/comment/css/comment.icon.theme.css
@@ -6,5 +6,5 @@
  */
 .field-icon-comment {
   color: red;
-  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='22' height='21' fill='none'%3e  %3cpath d='M13 20.5L10.2 17H5a1 1 0 0 1-1-1V5.103a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1V16a1 1 0 0 1-1 1h-5.2L13 20.5zm1.839-5.5H20V6.103H6V15h5.161L13 17.298 14.839 15zM1 0h17v2H2v11H0V1a1 1 0 0 1 1-1z' fill='%2355565b'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m21.0909 34-4.3273-5.4634h-8.03633c-.40988 0-.80297-.1645-1.0928-.4572-.28983-.2928-.45265-.6898-.45265-1.1038v-17.00994c0-.414.16282-.81104.45265-1.10378s.68292-.4572 1.0928-.4572h24.72723c.4099 0 .803.16446 1.0928.4572.2899.29274.4527.68978.4527 1.10378v17.00994c0 .414-.1628.811-.4527 1.1038-.2898.2927-.6829.4572-1.0928.4572h-8.0363zm2.8421-8.5854h7.9761v-13.888h-21.6364v13.888h7.9761l2.8421 3.5872zm-21.38755-23.4146h26.27275v3.12195h-24.72729v17.17075h-3.09091v-18.73172c0-.414.16282-.81104.45265-1.10378s.68292-.4572 1.0928-.4572z' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/comment/src/CommentTypeForm.php b/core/modules/comment/src/CommentTypeForm.php
index d992e11d205bc7b624452a834873b3f95dece57f..78f11c665f9081b78bd89a87d82e09942f9aa5f9 100644
--- a/core/modules/comment/src/CommentTypeForm.php
+++ b/core/modules/comment/src/CommentTypeForm.php
@@ -3,8 +3,8 @@
 namespace Drupal\comment;
 
 use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\language\Entity\ContentLanguageSettings;
@@ -83,6 +83,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#title' => $this->t('Label'),
       '#maxlength' => 255,
       '#default_value' => $comment_type->label(),
+      '#description' => $this->t('The human-readable name for this comment type, displayed on the <em>Comment types</em> page.'),
       '#required' => TRUE,
     ];
     $form['id'] = [
@@ -91,6 +92,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#machine_name' => [
         'exists' => '\Drupal\comment\Entity\CommentType::load',
       ],
+      '#description' => $this->t('Unique machine-readable name: lowercase letters, numbers, and underscores only.'),
       '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
       '#disabled' => !$comment_type->isNew(),
     ];
@@ -98,7 +100,7 @@ public function form(array $form, FormStateInterface $form_state) {
     $form['description'] = [
       '#type' => 'textarea',
       '#default_value' => $comment_type->getDescription(),
-      '#description' => $this->t('Describe this comment type. The text will be displayed on the <em>Comment types</em> administration overview page.'),
+      '#description' => $this->t('Displays on the <em>Comment types</em> page.'),
       '#title' => $this->t('Description'),
     ];
 
diff --git a/core/modules/comment/tests/src/Functional/CommentFieldsTest.php b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php
index 4d07112856a264e27d184e3d6664e27c1bc70a86..f67ebea79c7a45df4488ede97aedc464fb22953f 100644
--- a/core/modules/comment/tests/src/Functional/CommentFieldsTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php
@@ -158,12 +158,12 @@ public function testCommentFieldCreate() {
       'field_name' => 'user_comment',
     ];
     $this->drupalGet('admin/config/people/accounts/fields/add-field');
-    $this->submitForm($edit, 'Save and continue');
+    $this->submitForm($edit, 'Continue');
 
     // Try to save the comment field without selecting a comment type.
     $edit = [];
-    $this->drupalGet('admin/config/people/accounts/fields/user.user.field_user_comment/storage');
-    $this->submitForm($edit, 'Save field settings');
+    $this->drupalGet('admin/config/people/accounts/add-storage/user/field_user_comment');
+    $this->submitForm($edit, 'Continue');
     // We should get an error message.
     $this->assertSession()->pageTextContains('The submitted value in the Comment type element is not allowed.');
 
@@ -180,8 +180,8 @@ public function testCommentFieldCreate() {
     $edit = [
       'settings[comment_type]' => 'user_comment_type',
     ];
-    $this->drupalGet('admin/config/people/accounts/fields/user.user.field_user_comment/storage');
-    $this->submitForm($edit, 'Save field settings');
+    $this->drupalGet('admin/config/people/accounts/add-storage/user/field_user_comment');
+    $this->submitForm($edit, 'Continue');
     // We shouldn't get an error message.
     $this->assertSession()->pageTextNotContains('The submitted value in the Comment type element is not allowed.');
 
@@ -190,7 +190,7 @@ public function testCommentFieldCreate() {
     $edit = [
       'settings[per_page]' => 0,
     ];
-    $this->drupalGet('admin/config/people/accounts/fields/user.user.field_user_comment');
+    $this->drupalGet('admin/config/people/accounts/add-field/user/field_user_comment');
     $this->submitForm($edit, 'Save settings');
     $this->assertSession()->statusMessageContains('Saved User comment configuration.', 'status');
   }
diff --git a/core/modules/comment/tests/src/Functional/GenericTest.php b/core/modules/comment/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f6dc8a08545d271574db583a53bf2c2f2e452f62
--- /dev/null
+++ b/core/modules/comment/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\comment\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for comment.
+ *
+ * @group comment
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/comment/tests/src/Kernel/Views/CommentUserNameTest.php b/core/modules/comment/tests/src/Kernel/Views/CommentUserNameTest.php
index e65be6c6b584bb812158a9d47131a45eb2846e4c..5f1129c2f837a3d59e74079c61859789e5af1951 100644
--- a/core/modules/comment/tests/src/Kernel/Views/CommentUserNameTest.php
+++ b/core/modules/comment/tests/src/Kernel/Views/CommentUserNameTest.php
@@ -109,6 +109,7 @@ public function testUsername() {
     $view_id = $this->randomMachineName();
     $view = View::create([
       'id' => $view_id,
+      'label' => $view_id,
       'base_table' => 'comment_field_data',
       'display' => [
         'default' => [
diff --git a/core/modules/config/src/Form/ConfigSingleExportForm.php b/core/modules/config/src/Form/ConfigSingleExportForm.php
index c31daca71f6d07c220978f6fb8a5b1338ab5dc2d..2a7ac00f1078df6355e5c156df657a9a027000cc 100644
--- a/core/modules/config/src/Form/ConfigSingleExportForm.php
+++ b/core/modules/config/src/Form/ConfigSingleExportForm.php
@@ -154,8 +154,9 @@ public function updateExport($form, FormStateInterface $form_state) {
       $name = $form_state->getValue('config_name');
     }
     // Read the raw data for this config name, encode it, and display it.
-    $form['export']['#value'] = Yaml::encode($this->configStorage->read($name));
-    $form['export']['#description'] = $this->t('Filename: %name', ['%name' => $name . '.yml']);
+    $exists = $this->configStorage->exists($name);
+    $form['export']['#value'] = !$exists ? NULL : Yaml::encode($this->configStorage->read($name));
+    $form['export']['#description'] = !$exists ? NULL : $this->t('Filename: %name', ['%name' => $name . '.yml']);
     return $form['export'];
   }
 
@@ -187,7 +188,7 @@ protected function findConfiguration($config_type) {
       }, $this->definitions);
 
       // Find all config, and then filter our anything matching a config prefix.
-      $names = $this->configStorage->listAll();
+      $names += $this->configStorage->listAll();
       $names = array_combine($names, $names);
       foreach ($names as $config_name) {
         foreach ($config_prefixes as $config_prefix) {
diff --git a/core/modules/config/tests/config_install_dependency_enforced_combo_test/config/optional/config_test.dynamic.enforced_and_base_dependencies.yml b/core/modules/config/tests/config_install_dependency_enforced_combo_test/config/optional/config_test.dynamic.enforced_and_base_dependencies.yml
new file mode 100644
index 0000000000000000000000000000000000000000..923e47394f6c2f0e2d03d6a85b8628dc38956344
--- /dev/null
+++ b/core/modules/config/tests/config_install_dependency_enforced_combo_test/config/optional/config_test.dynamic.enforced_and_base_dependencies.yml
@@ -0,0 +1,13 @@
+id: config_test.dynamic.enforced_and_base_dependencies
+label: 'Module test with a mix of enforced and base modular dependencies'
+weight: 0
+style: ''
+status: true
+langcode: en
+protected_property: Default
+dependencies:
+  enforced:
+    module:
+      - config_install_dependency_enforced_combo_test
+  module:
+    - readonly_field_widget
diff --git a/core/modules/config/tests/config_install_dependency_enforced_combo_test/config_install_dependency_enforced_combo_test.info.yml b/core/modules/config/tests/config_install_dependency_enforced_combo_test/config_install_dependency_enforced_combo_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..75861bdff53a068cc8f595ccc38cba5f87547185
--- /dev/null
+++ b/core/modules/config/tests/config_install_dependency_enforced_combo_test/config_install_dependency_enforced_combo_test.info.yml
@@ -0,0 +1,4 @@
+name: 'Config install dependency test with mix of enforced and base module dependencies'
+type: module
+package: Testing
+version: VERSION
diff --git a/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
index 4dae83290481888ec0e08a12da42fe3ee420cc1f..2ef496055139506e9c8c14b6cd74d18f653fccf3 100644
--- a/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
+++ b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
@@ -120,6 +120,7 @@ config_schema_test.schema_data_types:
       type: sequence
       sequence:
         type: boolean
+    # @see \Drupal\Core\Config\Schema\Sequence::getElementDefinition()
     sequence_bc:
       type: sequence
       sequence:
@@ -220,7 +221,7 @@ wrapping.config_schema_test.plugin_types:
     tests:
       type: sequence
       sequence:
-        - type: wrapping.test.plugin_types.[plugin_id]
+        type: wrapping.test.plugin_types.[plugin_id]
 
 wrapping.test.plugin_types.*:
   type: test.plugin_types.[plugin_id]
@@ -240,7 +241,7 @@ wrapping.config_schema_test.double_brackets:
     tests:
       type: sequence
       sequence:
-        - type: wrapping.test.double_brackets.[another_key]
+        type: wrapping.test.double_brackets.[another_key]
 
 wrapping.test.double_brackets.*:
   type: test.double_brackets.[foo].[bar]
@@ -277,7 +278,7 @@ wrapping.config_schema_test.other_double_brackets:
     tests:
       type: sequence
       sequence:
-        - type: wrapping.test.other_double_brackets.[id]
+        type: wrapping.test.other_double_brackets.[id]
 
 wrapping.test.other_double_brackets.*:
   type: test.double_brackets.[id]
@@ -305,36 +306,36 @@ config_schema_test.schema_sequence_sort:
       type: sequence
       orderby: key
       sequence:
-        - type: string
+        type: string
     value_sort:
       type: sequence
       orderby: value
       sequence:
-        - type: string
+        type: string
     no_sort:
       type: sequence
       sequence:
-        - type: string
+        type: string
     complex_sort_value:
       type: sequence
       orderby: value
       sequence:
-        - type: mapping
-          mapping:
-            foo:
-              type: string
-            bar:
-              type: string
+        type: mapping
+        mapping:
+          foo:
+            type: string
+          bar:
+            type: string
     complex_sort_key:
       type: sequence
       orderby: key
       sequence:
-        - type: mapping
-          mapping:
-            foo:
-              type: string
-            bar:
-              type: string
+        type: mapping
+        mapping:
+          foo:
+            type: string
+          bar:
+            type: string
 
 config_schema_test.schema_mapping_sort:
   type: config_object
diff --git a/core/modules/config/tests/src/Functional/ConfigImportAllTest.php b/core/modules/config/tests/src/Functional/ConfigImportAllTest.php
index 10e6d4710140039228c89e2a7d64d1344824bc36..4d693c633378596bbcb674aefbb3afffa8b64723 100644
--- a/core/modules/config/tests/src/Functional/ConfigImportAllTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigImportAllTest.php
@@ -15,6 +15,7 @@
  * of all default configuration is also tested.
  *
  * @group config
+ * @group #slow
  */
 class ConfigImportAllTest extends ModuleTestBase {
 
diff --git a/core/modules/config/tests/src/Functional/ConfigImportUITest.php b/core/modules/config/tests/src/Functional/ConfigImportUITest.php
index bfb329686835b2148aeb6f251e2897b7c60cc7d6..35946054d232bff7381aeb7de6f3b050bbd0b340 100644
--- a/core/modules/config/tests/src/Functional/ConfigImportUITest.php
+++ b/core/modules/config/tests/src/Functional/ConfigImportUITest.php
@@ -10,6 +10,7 @@
  * Tests the user interface for importing configuration.
  *
  * @group config
+ * @group #slow
  */
 class ConfigImportUITest extends BrowserTestBase {
 
diff --git a/core/modules/config/tests/src/Functional/ConfigInstallWebTest.php b/core/modules/config/tests/src/Functional/ConfigInstallWebTest.php
index 314c3d50dbb4a3083b1e3e881bd8301264bb4010..f7035ec7a6a34efa9381e6f33edb2a50971aab89 100644
--- a/core/modules/config/tests/src/Functional/ConfigInstallWebTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigInstallWebTest.php
@@ -17,6 +17,7 @@
  * and uninstall functionality is tested.
  *
  * @group config
+ * @group #slow
  */
 class ConfigInstallWebTest extends BrowserTestBase {
 
diff --git a/core/modules/config/tests/src/Functional/ConfigOtherModuleTest.php b/core/modules/config/tests/src/Functional/ConfigOtherModuleTest.php
index 856ba976d3370dcb49171bb418428b240164c604..f6a1910112a13d22278711d3d0c2be4d09419240 100644
--- a/core/modules/config/tests/src/Functional/ConfigOtherModuleTest.php
+++ b/core/modules/config/tests/src/Functional/ConfigOtherModuleTest.php
@@ -90,6 +90,11 @@ public function testInstallOtherModuleFirst() {
     // recreated.
     $this->installModule('config');
     $this->assertNull($this->getStorage()->load('other_module_test_optional_entity_unmet'), 'The optional configuration config_test.dynamic.other_module_test_optional_entity_unmet whose dependencies are met is not installed when an unrelated module is installed.');
+
+    // Ensure that enforced dependencies do not overwrite base ones.
+    $this->installModule('config_install_dependency_enforced_combo_test');
+    $this->assertTrue(\Drupal::moduleHandler()->moduleExists('config_install_dependency_enforced_combo_test'), 'The config_install_dependency_enforced_combo_test module is installed.');
+    $this->assertNull($this->getStorage()->load('config_test.dynamic.enforced_and_base_dependencies'), 'The optional configuration config_test.dynamic.enforced_and_base_dependencies whose enforced dependencies are met but base module dependencies are not met is not created.');
   }
 
   /**
diff --git a/core/modules/config/tests/src/Functional/GenericTest.php b/core/modules/config/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..97215c102339ec5032efd90cf028686546ba7f20
--- /dev/null
+++ b/core/modules/config/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\config\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for config.
+ *
+ * @group config
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/config/tests/src/FunctionalJavascript/ConfigExportTest.php b/core/modules/config/tests/src/FunctionalJavascript/ConfigExportTest.php
index 69f4eb14562c575414eed5f36dc18e2c8ea06967..6ce94185a1a77b21999ee4ca6587420405572335 100644
--- a/core/modules/config/tests/src/FunctionalJavascript/ConfigExportTest.php
+++ b/core/modules/config/tests/src/FunctionalJavascript/ConfigExportTest.php
@@ -33,6 +33,7 @@ public function testAjaxOnExportPage() {
 
     // Check that the export is empty on load.
     $this->drupalGet('admin/config/development/configuration/single/export');
+    $this->assertTrue($this->assertSession()->optionExists('edit-config-name', '- Select -')->isSelected());
     $this->assertSession()->fieldValueEquals('export', '');
 
     // Check that the export is filled when selecting a config name.
@@ -40,6 +41,12 @@ public function testAjaxOnExportPage() {
     $this->assertSession()->assertWaitOnAjaxRequest();
     $this->assertSession()->fieldValueNotEquals('export', '');
 
+    // Check that the export is empty when selecting "- Select -" option in
+    // the config name.
+    $page->selectFieldOption('config_name', '- Select -');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->fieldValueEquals('export', '');
+
     // Check that the export is emptied again when selecting a config type.
     $page->selectFieldOption('config_type', 'Action');
     $this->assertSession()->assertWaitOnAjaxRequest();
diff --git a/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiModulesTest.php b/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiModulesTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c9aaa52844b837c2706f44113d2e0b874bce37a5
--- /dev/null
+++ b/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiModulesTest.php
@@ -0,0 +1,436 @@
+<?php
+
+namespace Drupal\Tests\config_translation\Functional;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Language\Language;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\node\Entity\NodeType;
+
+/**
+ * Translate settings and entities to various languages.
+ *
+ * @group config_translation
+ * @group #slow
+ */
+class ConfigTranslationUiModulesTest extends ConfigTranslationUiTestBase {
+
+  /**
+   * Tests the contact form translation.
+   */
+  public function testContactConfigEntityTranslation() {
+    $this->drupalLogin($this->adminUser);
+
+    $this->drupalGet('admin/structure/contact');
+
+    // Check for default contact form configuration entity from Contact module.
+    $this->assertSession()->linkByHrefExists('admin/structure/contact/manage/feedback');
+
+    // Save default language configuration.
+    $label = 'Send your feedback';
+    $edit = [
+      'label' => $label,
+      'recipients' => 'sales@example.com,support@example.com',
+      'reply' => 'Thank you for your mail',
+    ];
+    $this->drupalGet('admin/structure/contact/manage/feedback');
+    $this->submitForm($edit, 'Save');
+
+    // Ensure translation link is present.
+    $translation_base_url = 'admin/structure/contact/manage/feedback/translate';
+    $this->assertSession()->linkByHrefExists($translation_base_url);
+
+    // Make sure translate tab is present.
+    $this->drupalGet('admin/structure/contact/manage/feedback');
+    $this->assertSession()->linkExists('Translate contact form');
+
+    // Visit the form to confirm the changes.
+    $this->drupalGet('contact/feedback');
+    $this->assertSession()->pageTextContains($label);
+
+    foreach ($this->langcodes as $langcode) {
+      $this->drupalGet($translation_base_url);
+      $this->assertSession()->linkExists('Translate contact form');
+
+      // 'Add' link should be present for $langcode translation.
+      $translation_page_url = "$translation_base_url/$langcode/add";
+      $this->assertSession()->linkByHrefExists($translation_page_url);
+
+      // Make sure original text is present on this page.
+      $this->drupalGet($translation_page_url);
+      $this->assertSession()->pageTextContains($label);
+
+      // Update translatable fields.
+      $edit = [
+        'translation[config_names][contact.form.feedback][label]' => 'Website feedback - ' . $langcode,
+        'translation[config_names][contact.form.feedback][reply]' => 'Thank you for your mail - ' . $langcode,
+      ];
+
+      // Save language specific version of form.
+      $this->drupalGet($translation_page_url);
+      $this->submitForm($edit, 'Save translation');
+
+      // Expect translated values in language specific file.
+      $override = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'contact.form.feedback');
+      $expected = [
+        'label' => 'Website feedback - ' . $langcode,
+        'reply' => 'Thank you for your mail - ' . $langcode,
+      ];
+      $this->assertEquals($expected, $override->get());
+
+      // Check for edit, delete links (and no 'add' link) for $langcode.
+      $this->assertSession()->linkByHrefNotExists("$translation_base_url/$langcode/add");
+      $this->assertSession()->linkByHrefExists("$translation_base_url/$langcode/edit");
+      $this->assertSession()->linkByHrefExists("$translation_base_url/$langcode/delete");
+
+      // Visit language specific version of form to check label.
+      $this->drupalGet($langcode . '/contact/feedback');
+      $this->assertSession()->pageTextContains('Website feedback - ' . $langcode);
+
+      // Submit feedback.
+      $edit = [
+        'subject[0][value]' => 'Test subject',
+        'message[0][value]' => 'Test message',
+      ];
+      $this->submitForm($edit, 'Send message');
+    }
+
+    // Now that all language translations are present, check translation and
+    // original text all appear in any translated page on the translation
+    // forms.
+    foreach ($this->langcodes as $langcode) {
+      $langcode_prefixes = array_merge([''], $this->langcodes);
+      foreach ($langcode_prefixes as $langcode_prefix) {
+        $this->drupalGet(ltrim("$langcode_prefix/$translation_base_url/$langcode/edit", '/'));
+        $this->assertSession()->fieldValueEquals('translation[config_names][contact.form.feedback][label]', 'Website feedback - ' . $langcode);
+        $this->assertSession()->pageTextContains($label);
+      }
+    }
+
+    // We get all emails so no need to check inside the loop.
+    $captured_emails = $this->getMails();
+
+    // Check language specific auto reply text in email body.
+    foreach ($captured_emails as $email) {
+      if ($email['id'] == 'contact_page_autoreply') {
+        // Trim because we get an added newline for the body.
+        $this->assertEquals('Thank you for your mail - ' . $email['langcode'], trim($email['body']));
+      }
+    }
+
+    // Test that delete links work and operations perform properly.
+    foreach ($this->langcodes as $langcode) {
+      $language = \Drupal::languageManager()->getLanguage($langcode)->getName();
+
+      $this->drupalGet("$translation_base_url/$langcode/delete");
+      $this->assertSession()->pageTextContains("Are you sure you want to delete the $language translation of $label contact form?");
+      // Assert link back to list page to cancel delete is present.
+      $this->assertSession()->linkByHrefExists($translation_base_url);
+
+      $this->submitForm([], 'Delete');
+      $this->assertSession()->pageTextContains("$language translation of $label contact form was deleted");
+      $this->assertSession()->linkByHrefExists("$translation_base_url/$langcode/add");
+      $this->assertSession()->linkByHrefNotExists("translation_base_url/$langcode/edit");
+      $this->assertSession()->linkByHrefNotExists("$translation_base_url/$langcode/delete");
+
+      // Expect no language specific file present anymore.
+      $override = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'contact.form.feedback');
+      $this->assertTrue($override->isNew());
+    }
+
+    // Check configuration page with translator user. Should have no access.
+    $this->drupalLogout();
+    $this->drupalLogin($this->translatorUser);
+    $this->drupalGet('admin/structure/contact/manage/feedback');
+    $this->assertSession()->statusCodeEquals(403);
+
+    // While translator can access the translation page, the edit link is not
+    // present due to lack of permissions.
+    $this->drupalGet($translation_base_url);
+    $this->assertSession()->linkNotExists('Edit');
+
+    // Check 'Add' link for French.
+    $this->assertSession()->linkByHrefExists("$translation_base_url/fr/add");
+  }
+
+  /**
+   * Tests the views translation interface.
+   */
+  public function testViewsTranslationUI() {
+    $this->drupalLogin($this->adminUser);
+
+    $description = 'All content promoted to the front page.';
+    $human_readable_name = 'Frontpage';
+    $display_settings_default = 'Default';
+    $display_options_default = '(Empty)';
+    $translation_base_url = 'admin/structure/views/view/frontpage/translate';
+
+    $this->drupalGet($translation_base_url);
+
+    // Check 'Add' link of French to visit add page.
+    $this->assertSession()->linkByHrefExists("$translation_base_url/fr/add");
+    $this->clickLink('Add');
+
+    // Make sure original text is present on this page.
+    $this->assertSession()->pageTextContains($description);
+    $this->assertSession()->pageTextContains($human_readable_name);
+
+    // Update Views Fields for French.
+    $edit = [
+      'translation[config_names][views.view.frontpage][description]' => $description . " FR",
+      'translation[config_names][views.view.frontpage][label]' => $human_readable_name . " FR",
+      'translation[config_names][views.view.frontpage][display][default][display_title]' => $display_settings_default . " FR",
+      'translation[config_names][views.view.frontpage][display][default][display_options][title]' => $display_options_default . " FR",
+    ];
+    $this->drupalGet("{$translation_base_url}/fr/add");
+    $this->submitForm($edit, 'Save translation');
+    $this->assertSession()->pageTextContains('Successfully saved French translation.');
+
+    // Check for edit, delete links (and no 'add' link) for French language.
+    $this->assertSession()->linkByHrefNotExists("$translation_base_url/fr/add");
+    $this->assertSession()->linkByHrefExists("$translation_base_url/fr/edit");
+    $this->assertSession()->linkByHrefExists("$translation_base_url/fr/delete");
+
+    // Check translation saved proper.
+    $this->drupalGet("$translation_base_url/fr/edit");
+    $this->assertSession()->fieldValueEquals('translation[config_names][views.view.frontpage][description]', $description . " FR");
+    $this->assertSession()->fieldValueEquals('translation[config_names][views.view.frontpage][label]', $human_readable_name . " FR");
+    $this->assertSession()->fieldValueEquals('translation[config_names][views.view.frontpage][display][default][display_title]', $display_settings_default . " FR");
+    $this->assertSession()->fieldValueEquals('translation[config_names][views.view.frontpage][display][default][display_options][title]', $display_options_default . " FR");
+  }
+
+  /**
+   * Tests the translation of field and field storage configuration.
+   */
+  public function testFieldConfigTranslation() {
+    // Add a test field which has a translatable field setting and a
+    // translatable field storage setting.
+    $field_name = $this->randomMachineName();
+    $field_storage = FieldStorageConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'type' => 'test_field',
+    ]);
+
+    $translatable_storage_setting = $this->randomString();
+    $field_storage->setSetting('translatable_storage_setting', $translatable_storage_setting);
+    $field_storage->save();
+
+    $bundle = $this->randomMachineName();
+    entity_test_create_bundle($bundle);
+    $field = FieldConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'bundle' => $bundle,
+    ]);
+
+    $translatable_field_setting = $this->randomString();
+    $field->setSetting('translatable_field_setting', $translatable_field_setting);
+    $field->save();
+
+    $this->drupalLogin($this->translatorUser);
+
+    $this->drupalGet("/entity_test/structure/$bundle/fields/entity_test.$bundle.$field_name/translate");
+    $this->clickLink('Add');
+
+    $this->assertSession()->pageTextContains('Translatable field setting');
+    $this->assertSession()->assertEscaped($translatable_field_setting);
+    $this->assertSession()->pageTextContains('Translatable storage setting');
+    $this->assertSession()->assertEscaped($translatable_storage_setting);
+  }
+
+  /**
+   * Tests the translation of a boolean field settings.
+   */
+  public function testBooleanFieldConfigTranslation() {
+    // Add a test boolean field.
+    $field_name = $this->randomMachineName();
+    FieldStorageConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'type' => 'boolean',
+    ])->save();
+
+    $bundle = $this->randomMachineName();
+    entity_test_create_bundle($bundle);
+    $field = FieldConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => 'entity_test',
+      'bundle' => $bundle,
+    ]);
+
+    $on_label = 'On label (with <em>HTML</em> & things)';
+    $field->setSetting('on_label', $on_label);
+    $off_label = 'Off label (with <em>HTML</em> & things)';
+    $field->setSetting('off_label', $off_label);
+    $field->save();
+
+    $this->drupalLogin($this->translatorUser);
+
+    $this->drupalGet("/entity_test/structure/$bundle/fields/entity_test.$bundle.$field_name/translate");
+    $this->clickLink('Add');
+
+    // Checks the text of details summary element that surrounds the translation
+    // options.
+    $this->assertSession()->responseContains(Html::escape(strip_tags($on_label)) . ' Boolean settings');
+
+    // Checks that the correct on and off labels appear on the form.
+    $this->assertSession()->assertEscaped($on_label);
+    $this->assertSession()->assertEscaped($off_label);
+  }
+
+  /**
+   * Tests text_format translation.
+   */
+  public function testTextFormatTranslation() {
+    $this->drupalLogin($this->adminUser);
+    /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
+    $config_factory = $this->container->get('config.factory');
+
+    $expected = [
+      'value' => '<p><strong>Hello World</strong></p>',
+      'format' => 'plain_text',
+    ];
+    $actual = $config_factory
+      ->get('config_translation_test.content')
+      ->getOriginal('content', FALSE);
+    $this->assertEquals($expected, $actual);
+
+    $translation_base_url = 'admin/config/media/file-system/translate';
+    $this->drupalGet($translation_base_url);
+
+    // 'Add' link should be present for French translation.
+    $translation_page_url = "$translation_base_url/fr/add";
+    $this->assertSession()->linkByHrefExists($translation_page_url);
+
+    $this->drupalGet($translation_page_url);
+
+    // Assert that changing the text format is not possible, even for an
+    // administrator.
+    $this->assertSession()->fieldNotExists('translation[config_names][config_translation_test.content][content][format]');
+
+    // Update translatable fields.
+    $edit = [
+      'translation[config_names][config_translation_test.content][content][value]' => '<p><strong>Hello World</strong> - FR</p>',
+    ];
+
+    // Save language specific version of form.
+    $this->drupalGet($translation_page_url);
+    $this->submitForm($edit, 'Save translation');
+
+    // Get translation and check we've got the right value.
+    $expected = [
+      'value' => '<p><strong>Hello World</strong> - FR</p>',
+      'format' => 'plain_text',
+    ];
+    $this->container->get('language.config_factory_override')
+      ->setLanguage(new Language(['id' => 'fr']));
+    $actual = $config_factory
+      ->get('config_translation_test.content')
+      ->get('content');
+    $this->assertEquals($expected, $actual);
+
+    // Change the text format of the source configuration and verify that the
+    // text format of the translation does not change because that could lead to
+    // security vulnerabilities.
+    $config_factory
+      ->getEditable('config_translation_test.content')
+      ->set('content.format', 'full_html')
+      ->save();
+
+    $actual = $config_factory
+      ->get('config_translation_test.content')
+      ->get('content');
+    // The translation should not have changed, so re-use $expected.
+    $this->assertEquals($expected, $actual);
+
+    // Because the text is now in a text format that the translator does not
+    // have access to, the translator should not be able to translate it.
+    $translation_page_url = "$translation_base_url/fr/edit";
+    $this->drupalLogin($this->translatorUser);
+    $this->drupalGet($translation_page_url);
+    $this->assertDisabledTextarea('edit-translation-config-names-config-translation-testcontent-content-value');
+    $this->submitForm([], 'Save translation');
+    // Check that submitting the form did not update the text format of the
+    // translation.
+    $actual = $config_factory
+      ->get('config_translation_test.content')
+      ->get('content');
+    $this->assertEquals($expected, $actual);
+
+    // The administrator must explicitly change the text format.
+    $this->drupalLogin($this->adminUser);
+    $edit = [
+      'translation[config_names][config_translation_test.content][content][format]' => 'full_html',
+    ];
+    $this->drupalGet($translation_page_url);
+    $this->submitForm($edit, 'Save translation');
+    $expected = [
+      'value' => '<p><strong>Hello World</strong> - FR</p>',
+      'format' => 'full_html',
+    ];
+    $actual = $config_factory
+      ->get('config_translation_test.content')
+      ->get('content');
+    $this->assertEquals($expected, $actual);
+  }
+
+  /**
+   * Tests field translation for node fields.
+   */
+  public function testNodeFieldTranslation() {
+    NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
+
+    $field_name = 'translatable_field';
+    $field_storage = FieldStorageConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => 'node',
+      'type' => 'text',
+    ]);
+
+    $field_storage->setSetting('translatable_storage_setting', 'translatable_storage_setting');
+    $field_storage->save();
+    $field = FieldConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => 'node',
+      'bundle' => 'article',
+    ]);
+    $field->save();
+
+    $this->drupalLogin($this->translatorUser);
+
+    $this->drupalGet("/entity_test/structure/article/fields/node.article.$field_name/translate");
+    $this->clickLink('Add');
+
+    $form_values = [
+      'translation[config_names][field.field.node.article.translatable_field][description]' => 'FR Help text.',
+      'translation[config_names][field.field.node.article.translatable_field][label]' => 'FR label',
+    ];
+    $this->submitForm($form_values, 'Save translation');
+    $this->assertSession()->pageTextContains('Successfully saved French translation.');
+
+    // Check that the translations are saved.
+    $this->clickLink('Add');
+    $this->assertSession()->responseContains('FR label');
+  }
+
+  /**
+   * Test translation save confirmation message.
+   */
+  public function testMenuTranslationWithoutChange() {
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('admin/structure/menu/manage/main/translate/tyv/add');
+    $this->submitForm([], 'Save translation');
+    $this->assertSession()->pageTextContains('Tuvan translation was not added. To add a translation, you must modify the configuration.');
+
+    $this->drupalGet('admin/structure/menu/manage/main/translate/tyv/add');
+    $edit = [
+      'translation[config_names][system.menu.main][label]' => 'Main navigation Translation',
+      'translation[config_names][system.menu.main][description]' => 'Site section links Translation',
+    ];
+    $this->submitForm($edit, 'Save translation');
+    $this->assertSession()->pageTextContains('Successfully saved Tuvan translation.');
+  }
+
+}
diff --git a/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiTest.php b/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiTest.php
index 688009de5bbe1cc978080497412e6f0f888266ad..b8151f08fd765aa1461961389769f9ec054d9e20 100644
--- a/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiTest.php
@@ -2,16 +2,8 @@
 
 namespace Drupal\Tests\config_translation\Functional;
 
-use Drupal\Component\Utility\Html;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageInterface;
-use Drupal\Core\Test\AssertMailTrait;
-use Drupal\field\Entity\FieldConfig;
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\filter\Entity\FilterFormat;
-use Drupal\language\Entity\ConfigurableLanguage;
-use Drupal\node\Entity\NodeType;
-use Drupal\Tests\BrowserTestBase;
 
 // cspell:ignore viewsviewfiles
 
@@ -19,116 +11,9 @@
  * Translate settings and entities to various languages.
  *
  * @group config_translation
+ * @group #slow
  */
-class ConfigTranslationUiTest extends BrowserTestBase {
-
-  use AssertMailTrait;
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  protected static $modules = [
-    'block',
-    'config_translation',
-    'config_translation_test',
-    'contact',
-    'contact_test',
-    'contextual',
-    'entity_test',
-    'field_test',
-    'field_ui',
-    'filter',
-    'filter_test',
-    'node',
-    'views',
-    'views_ui',
-    'menu_ui',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $defaultTheme = 'stark';
-
-  /**
-   * Languages to enable.
-   *
-   * @var array
-   */
-  protected $langcodes = ['fr', 'ta', 'tyv'];
-
-  /**
-   * Administrator user for tests.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $adminUser;
-
-  /**
-   * Translator user for tests.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $translatorUser;
-
-  /**
-   * String translation storage object.
-   *
-   * @var \Drupal\locale\StringStorageInterface
-   */
-  protected $localeStorage;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    $translator_permissions = [
-      'translate configuration',
-    ];
-
-    /** @var \Drupal\filter\FilterFormatInterface $filter_test_format */
-    $filter_test_format = FilterFormat::load('filter_test');
-    /** @var \Drupal\filter\FilterFormatInterface $filtered_html_format */
-    $filtered_html_format = FilterFormat::load('filtered_html');
-    /** @var \Drupal\filter\FilterFormatInterface $full_html_format */
-    $full_html_format = FilterFormat::load('full_html');
-
-    $admin_permissions = array_merge(
-      $translator_permissions,
-      [
-        'administer languages',
-        'administer site configuration',
-        'link to any page',
-        'administer contact forms',
-        'administer filters',
-        $filtered_html_format->getPermissionName(),
-        $full_html_format->getPermissionName(),
-        $filter_test_format->getPermissionName(),
-        'access site-wide contact form',
-        'access contextual links',
-        'administer views',
-        'administer account settings',
-        'administer themes',
-        'bypass node access',
-        'administer content types',
-        'translate interface',
-      ]
-    );
-    // Create and log in user.
-    $this->translatorUser = $this->drupalCreateUser($translator_permissions);
-    $this->adminUser = $this->drupalCreateUser($admin_permissions);
-
-    // Add languages.
-    foreach ($this->langcodes as $langcode) {
-      ConfigurableLanguage::createFromLangcode($langcode)->save();
-    }
-    $this->localeStorage = $this->container->get('locale.storage');
-    $this->drupalPlaceBlock('local_tasks_block');
-    $this->drupalPlaceBlock('page_title_block');
-  }
+class ConfigTranslationUiTest extends ConfigTranslationUiTestBase {
 
   /**
    * Tests the site information translation interface.
@@ -235,6 +120,72 @@ public function testSiteInformationTranslationUi() {
     $this->assertSession()->pageTextNotContains($fr_site_name_label);
   }
 
+  /**
+   * Tests date format translation.
+   */
+  public function testDateFormatTranslation() {
+    $this->drupalLogin($this->adminUser);
+
+    $this->drupalGet('admin/config/regional/date-time');
+
+    // Check for medium format.
+    $this->assertSession()->linkByHrefExists('admin/config/regional/date-time/formats/manage/medium');
+
+    // Save default language configuration for a new format.
+    $edit = [
+      'label' => 'Custom medium date',
+      'id' => 'custom_medium',
+      'date_format_pattern' => 'Y. m. d. H:i',
+    ];
+    $this->drupalGet('admin/config/regional/date-time/formats/add');
+    $this->submitForm($edit, 'Add format');
+
+    // Test translating a default shipped format and our custom format.
+    $formats = [
+      'medium' => 'Default medium date',
+      'custom_medium' => 'Custom medium date',
+    ];
+    foreach ($formats as $id => $label) {
+      $translation_base_url = 'admin/config/regional/date-time/formats/manage/' . $id . '/translate';
+
+      $this->drupalGet($translation_base_url);
+
+      // 'Add' link should be present for French translation.
+      $translation_page_url = "$translation_base_url/fr/add";
+      $this->assertSession()->linkByHrefExists($translation_page_url);
+
+      // Make sure original text is present on this page.
+      $this->drupalGet($translation_page_url);
+      $this->assertSession()->pageTextContains($label);
+
+      // Make sure that the date library is added.
+      $this->assertSession()->responseContains('core/modules/system/js/system.date.js');
+
+      // Update translatable fields.
+      $edit = [
+        'translation[config_names][core.date_format.' . $id . '][label]' => $id . ' - FR',
+        'translation[config_names][core.date_format.' . $id . '][pattern]' => 'D',
+      ];
+
+      // Save language specific version of form.
+      $this->drupalGet($translation_page_url);
+      $this->submitForm($edit, 'Save translation');
+
+      // Get translation and check we've got the right value.
+      $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'core.date_format.' . $id);
+      $expected = [
+        'label' => $id . ' - FR',
+        'pattern' => 'D',
+      ];
+      $this->assertEquals($expected, $override->get());
+
+      // Formatting the date 8 / 27 / 1985 @ 13:37 EST with pattern D should
+      // display "Tue".
+      $formatted_date = $this->container->get('date.formatter')->format(494015820, $id, NULL, 'America/New_York', 'fr');
+      $this->assertEquals('Tue', $formatted_date, 'Got the right formatted date using the date format translation pattern.');
+    }
+  }
+
   /**
    * Tests the site information translation interface.
    */
@@ -313,210 +264,6 @@ public function testSourceValueDuplicateSave() {
     $this->assertSession()->linkByHrefExists("$translation_base_url/fr/add");
   }
 
-  /**
-   * Tests the contact form translation.
-   */
-  public function testContactConfigEntityTranslation() {
-    $this->drupalLogin($this->adminUser);
-
-    $this->drupalGet('admin/structure/contact');
-
-    // Check for default contact form configuration entity from Contact module.
-    $this->assertSession()->linkByHrefExists('admin/structure/contact/manage/feedback');
-
-    // Save default language configuration.
-    $label = 'Send your feedback';
-    $edit = [
-      'label' => $label,
-      'recipients' => 'sales@example.com,support@example.com',
-      'reply' => 'Thank you for your mail',
-    ];
-    $this->drupalGet('admin/structure/contact/manage/feedback');
-    $this->submitForm($edit, 'Save');
-
-    // Ensure translation link is present.
-    $translation_base_url = 'admin/structure/contact/manage/feedback/translate';
-    $this->assertSession()->linkByHrefExists($translation_base_url);
-
-    // Make sure translate tab is present.
-    $this->drupalGet('admin/structure/contact/manage/feedback');
-    $this->assertSession()->linkExists('Translate contact form');
-
-    // Visit the form to confirm the changes.
-    $this->drupalGet('contact/feedback');
-    $this->assertSession()->pageTextContains($label);
-
-    foreach ($this->langcodes as $langcode) {
-      $this->drupalGet($translation_base_url);
-      $this->assertSession()->linkExists('Translate contact form');
-
-      // 'Add' link should be present for $langcode translation.
-      $translation_page_url = "$translation_base_url/$langcode/add";
-      $this->assertSession()->linkByHrefExists($translation_page_url);
-
-      // Make sure original text is present on this page.
-      $this->drupalGet($translation_page_url);
-      $this->assertSession()->pageTextContains($label);
-
-      // Update translatable fields.
-      $edit = [
-        'translation[config_names][contact.form.feedback][label]' => 'Website feedback - ' . $langcode,
-        'translation[config_names][contact.form.feedback][reply]' => 'Thank you for your mail - ' . $langcode,
-      ];
-
-      // Save language specific version of form.
-      $this->drupalGet($translation_page_url);
-      $this->submitForm($edit, 'Save translation');
-
-      // Expect translated values in language specific file.
-      $override = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'contact.form.feedback');
-      $expected = [
-        'label' => 'Website feedback - ' . $langcode,
-        'reply' => 'Thank you for your mail - ' . $langcode,
-      ];
-      $this->assertEquals($expected, $override->get());
-
-      // Check for edit, delete links (and no 'add' link) for $langcode.
-      $this->assertSession()->linkByHrefNotExists("$translation_base_url/$langcode/add");
-      $this->assertSession()->linkByHrefExists("$translation_base_url/$langcode/edit");
-      $this->assertSession()->linkByHrefExists("$translation_base_url/$langcode/delete");
-
-      // Visit language specific version of form to check label.
-      $this->drupalGet($langcode . '/contact/feedback');
-      $this->assertSession()->pageTextContains('Website feedback - ' . $langcode);
-
-      // Submit feedback.
-      $edit = [
-        'subject[0][value]' => 'Test subject',
-        'message[0][value]' => 'Test message',
-      ];
-      $this->submitForm($edit, 'Send message');
-    }
-
-    // Now that all language translations are present, check translation and
-    // original text all appear in any translated page on the translation
-    // forms.
-    foreach ($this->langcodes as $langcode) {
-      $langcode_prefixes = array_merge([''], $this->langcodes);
-      foreach ($langcode_prefixes as $langcode_prefix) {
-        $this->drupalGet(ltrim("$langcode_prefix/$translation_base_url/$langcode/edit", '/'));
-        $this->assertSession()->fieldValueEquals('translation[config_names][contact.form.feedback][label]', 'Website feedback - ' . $langcode);
-        $this->assertSession()->pageTextContains($label);
-      }
-    }
-
-    // We get all emails so no need to check inside the loop.
-    $captured_emails = $this->getMails();
-
-    // Check language specific auto reply text in email body.
-    foreach ($captured_emails as $email) {
-      if ($email['id'] == 'contact_page_autoreply') {
-        // Trim because we get an added newline for the body.
-        $this->assertEquals('Thank you for your mail - ' . $email['langcode'], trim($email['body']));
-      }
-    }
-
-    // Test that delete links work and operations perform properly.
-    foreach ($this->langcodes as $langcode) {
-      $language = \Drupal::languageManager()->getLanguage($langcode)->getName();
-
-      $this->drupalGet("$translation_base_url/$langcode/delete");
-      $this->assertSession()->pageTextContains("Are you sure you want to delete the $language translation of $label contact form?");
-      // Assert link back to list page to cancel delete is present.
-      $this->assertSession()->linkByHrefExists($translation_base_url);
-
-      $this->submitForm([], 'Delete');
-      $this->assertSession()->pageTextContains("$language translation of $label contact form was deleted");
-      $this->assertSession()->linkByHrefExists("$translation_base_url/$langcode/add");
-      $this->assertSession()->linkByHrefNotExists("translation_base_url/$langcode/edit");
-      $this->assertSession()->linkByHrefNotExists("$translation_base_url/$langcode/delete");
-
-      // Expect no language specific file present anymore.
-      $override = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'contact.form.feedback');
-      $this->assertTrue($override->isNew());
-    }
-
-    // Check configuration page with translator user. Should have no access.
-    $this->drupalLogout();
-    $this->drupalLogin($this->translatorUser);
-    $this->drupalGet('admin/structure/contact/manage/feedback');
-    $this->assertSession()->statusCodeEquals(403);
-
-    // While translator can access the translation page, the edit link is not
-    // present due to lack of permissions.
-    $this->drupalGet($translation_base_url);
-    $this->assertSession()->linkNotExists('Edit');
-
-    // Check 'Add' link for French.
-    $this->assertSession()->linkByHrefExists("$translation_base_url/fr/add");
-  }
-
-  /**
-   * Tests date format translation.
-   */
-  public function testDateFormatTranslation() {
-    $this->drupalLogin($this->adminUser);
-
-    $this->drupalGet('admin/config/regional/date-time');
-
-    // Check for medium format.
-    $this->assertSession()->linkByHrefExists('admin/config/regional/date-time/formats/manage/medium');
-
-    // Save default language configuration for a new format.
-    $edit = [
-      'label' => 'Custom medium date',
-      'id' => 'custom_medium',
-      'date_format_pattern' => 'Y. m. d. H:i',
-    ];
-    $this->drupalGet('admin/config/regional/date-time/formats/add');
-    $this->submitForm($edit, 'Add format');
-
-    // Test translating a default shipped format and our custom format.
-    $formats = [
-      'medium' => 'Default medium date',
-      'custom_medium' => 'Custom medium date',
-    ];
-    foreach ($formats as $id => $label) {
-      $translation_base_url = 'admin/config/regional/date-time/formats/manage/' . $id . '/translate';
-
-      $this->drupalGet($translation_base_url);
-
-      // 'Add' link should be present for French translation.
-      $translation_page_url = "$translation_base_url/fr/add";
-      $this->assertSession()->linkByHrefExists($translation_page_url);
-
-      // Make sure original text is present on this page.
-      $this->drupalGet($translation_page_url);
-      $this->assertSession()->pageTextContains($label);
-
-      // Make sure that the date library is added.
-      $this->assertSession()->responseContains('core/modules/system/js/system.date.js');
-
-      // Update translatable fields.
-      $edit = [
-        'translation[config_names][core.date_format.' . $id . '][label]' => $id . ' - FR',
-        'translation[config_names][core.date_format.' . $id . '][pattern]' => 'D',
-      ];
-
-      // Save language specific version of form.
-      $this->drupalGet($translation_page_url);
-      $this->submitForm($edit, 'Save translation');
-
-      // Get translation and check we've got the right value.
-      $override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'core.date_format.' . $id);
-      $expected = [
-        'label' => $id . ' - FR',
-        'pattern' => 'D',
-      ];
-      $this->assertEquals($expected, $override->get());
-
-      // Formatting the date 8 / 27 / 1985 @ 13:37 EST with pattern D should
-      // display "Tue".
-      $formatted_date = $this->container->get('date.formatter')->format(494015820, $id, NULL, 'America/New_York', 'fr');
-      $this->assertEquals('Tue', $formatted_date, 'Got the right formatted date using the date format translation pattern.');
-    }
-  }
-
   /**
    * Tests the account settings translation interface.
    *
@@ -601,52 +348,6 @@ public function testSourceAndTargetLanguage() {
     $this->assertSession()->statusCodeEquals(403);
   }
 
-  /**
-   * Tests the views translation interface.
-   */
-  public function testViewsTranslationUI() {
-    $this->drupalLogin($this->adminUser);
-
-    $description = 'All content promoted to the front page.';
-    $human_readable_name = 'Frontpage';
-    $display_settings_default = 'Default';
-    $display_options_default = '(Empty)';
-    $translation_base_url = 'admin/structure/views/view/frontpage/translate';
-
-    $this->drupalGet($translation_base_url);
-
-    // Check 'Add' link of French to visit add page.
-    $this->assertSession()->linkByHrefExists("$translation_base_url/fr/add");
-    $this->clickLink('Add');
-
-    // Make sure original text is present on this page.
-    $this->assertSession()->pageTextContains($description);
-    $this->assertSession()->pageTextContains($human_readable_name);
-
-    // Update Views Fields for French.
-    $edit = [
-      'translation[config_names][views.view.frontpage][description]' => $description . " FR",
-      'translation[config_names][views.view.frontpage][label]' => $human_readable_name . " FR",
-      'translation[config_names][views.view.frontpage][display][default][display_title]' => $display_settings_default . " FR",
-      'translation[config_names][views.view.frontpage][display][default][display_options][title]' => $display_options_default . " FR",
-    ];
-    $this->drupalGet("{$translation_base_url}/fr/add");
-    $this->submitForm($edit, 'Save translation');
-    $this->assertSession()->pageTextContains('Successfully saved French translation.');
-
-    // Check for edit, delete links (and no 'add' link) for French language.
-    $this->assertSession()->linkByHrefNotExists("$translation_base_url/fr/add");
-    $this->assertSession()->linkByHrefExists("$translation_base_url/fr/edit");
-    $this->assertSession()->linkByHrefExists("$translation_base_url/fr/delete");
-
-    // Check translation saved proper.
-    $this->drupalGet("$translation_base_url/fr/edit");
-    $this->assertSession()->fieldValueEquals('translation[config_names][views.view.frontpage][description]', $description . " FR");
-    $this->assertSession()->fieldValueEquals('translation[config_names][views.view.frontpage][label]', $human_readable_name . " FR");
-    $this->assertSession()->fieldValueEquals('translation[config_names][views.view.frontpage][display][default][display_title]', $display_settings_default . " FR");
-    $this->assertSession()->fieldValueEquals('translation[config_names][views.view.frontpage][display][default][display_options][title]', $display_options_default . " FR");
-  }
-
   /**
    * Tests plural source elements in configuration translation forms.
    */
@@ -740,86 +441,6 @@ public function testPluralConfigStrings() {
     $this->assertSession()->fieldValueEquals('translation[config_names][views.view.files][display][default][display_options][fields][count][format_plural_string][3]', "$field_value_plural 3 SL");
   }
 
-  /**
-   * Tests the translation of field and field storage configuration.
-   */
-  public function testFieldConfigTranslation() {
-    // Add a test field which has a translatable field setting and a
-    // translatable field storage setting.
-    $field_name = $this->randomMachineName();
-    $field_storage = FieldStorageConfig::create([
-      'field_name' => $field_name,
-      'entity_type' => 'entity_test',
-      'type' => 'test_field',
-    ]);
-
-    $translatable_storage_setting = $this->randomString();
-    $field_storage->setSetting('translatable_storage_setting', $translatable_storage_setting);
-    $field_storage->save();
-
-    $bundle = $this->randomMachineName();
-    entity_test_create_bundle($bundle);
-    $field = FieldConfig::create([
-      'field_name' => $field_name,
-      'entity_type' => 'entity_test',
-      'bundle' => $bundle,
-    ]);
-
-    $translatable_field_setting = $this->randomString();
-    $field->setSetting('translatable_field_setting', $translatable_field_setting);
-    $field->save();
-
-    $this->drupalLogin($this->translatorUser);
-
-    $this->drupalGet("/entity_test/structure/$bundle/fields/entity_test.$bundle.$field_name/translate");
-    $this->clickLink('Add');
-
-    $this->assertSession()->pageTextContains('Translatable field setting');
-    $this->assertSession()->assertEscaped($translatable_field_setting);
-    $this->assertSession()->pageTextContains('Translatable storage setting');
-    $this->assertSession()->assertEscaped($translatable_storage_setting);
-  }
-
-  /**
-   * Tests the translation of a boolean field settings.
-   */
-  public function testBooleanFieldConfigTranslation() {
-    // Add a test boolean field.
-    $field_name = $this->randomMachineName();
-    FieldStorageConfig::create([
-      'field_name' => $field_name,
-      'entity_type' => 'entity_test',
-      'type' => 'boolean',
-    ])->save();
-
-    $bundle = $this->randomMachineName();
-    entity_test_create_bundle($bundle);
-    $field = FieldConfig::create([
-      'field_name' => $field_name,
-      'entity_type' => 'entity_test',
-      'bundle' => $bundle,
-    ]);
-
-    $on_label = 'On label (with <em>HTML</em> & things)';
-    $field->setSetting('on_label', $on_label);
-    $off_label = 'Off label (with <em>HTML</em> & things)';
-    $field->setSetting('off_label', $off_label);
-    $field->save();
-
-    $this->drupalLogin($this->translatorUser);
-
-    $this->drupalGet("/entity_test/structure/$bundle/fields/entity_test.$bundle.$field_name/translate");
-    $this->clickLink('Add');
-
-    // Checks the text of details summary element that surrounds the translation
-    // options.
-    $this->assertSession()->responseContains(Html::escape(strip_tags($on_label)) . ' Boolean settings');
-
-    // Checks that the correct on and off labels appear on the form.
-    $this->assertSession()->assertEscaped($on_label);
-    $this->assertSession()->assertEscaped($off_label);
-  }
-
   /**
    * Tests translation storage in locale storage.
    */
@@ -959,266 +580,4 @@ public function testSequenceTranslation() {
     $this->assertEquals($expected, $actual);
   }
 
-  /**
-   * Tests text_format translation.
-   */
-  public function testTextFormatTranslation() {
-    $this->drupalLogin($this->adminUser);
-    /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
-    $config_factory = $this->container->get('config.factory');
-
-    $expected = [
-      'value' => '<p><strong>Hello World</strong></p>',
-      'format' => 'plain_text',
-    ];
-    $actual = $config_factory
-      ->get('config_translation_test.content')
-      ->getOriginal('content', FALSE);
-    $this->assertEquals($expected, $actual);
-
-    $translation_base_url = 'admin/config/media/file-system/translate';
-    $this->drupalGet($translation_base_url);
-
-    // 'Add' link should be present for French translation.
-    $translation_page_url = "$translation_base_url/fr/add";
-    $this->assertSession()->linkByHrefExists($translation_page_url);
-
-    $this->drupalGet($translation_page_url);
-
-    // Assert that changing the text format is not possible, even for an
-    // administrator.
-    $this->assertSession()->fieldNotExists('translation[config_names][config_translation_test.content][content][format]');
-
-    // Update translatable fields.
-    $edit = [
-      'translation[config_names][config_translation_test.content][content][value]' => '<p><strong>Hello World</strong> - FR</p>',
-    ];
-
-    // Save language specific version of form.
-    $this->drupalGet($translation_page_url);
-    $this->submitForm($edit, 'Save translation');
-
-    // Get translation and check we've got the right value.
-    $expected = [
-      'value' => '<p><strong>Hello World</strong> - FR</p>',
-      'format' => 'plain_text',
-    ];
-    $this->container->get('language.config_factory_override')
-      ->setLanguage(new Language(['id' => 'fr']));
-    $actual = $config_factory
-      ->get('config_translation_test.content')
-      ->get('content');
-    $this->assertEquals($expected, $actual);
-
-    // Change the text format of the source configuration and verify that the
-    // text format of the translation does not change because that could lead to
-    // security vulnerabilities.
-    $config_factory
-      ->getEditable('config_translation_test.content')
-      ->set('content.format', 'full_html')
-      ->save();
-
-    $actual = $config_factory
-      ->get('config_translation_test.content')
-      ->get('content');
-    // The translation should not have changed, so re-use $expected.
-    $this->assertEquals($expected, $actual);
-
-    // Because the text is now in a text format that the translator does not
-    // have access to, the translator should not be able to translate it.
-    $translation_page_url = "$translation_base_url/fr/edit";
-    $this->drupalLogin($this->translatorUser);
-    $this->drupalGet($translation_page_url);
-    $this->assertDisabledTextarea('edit-translation-config-names-config-translation-testcontent-content-value');
-    $this->submitForm([], 'Save translation');
-    // Check that submitting the form did not update the text format of the
-    // translation.
-    $actual = $config_factory
-      ->get('config_translation_test.content')
-      ->get('content');
-    $this->assertEquals($expected, $actual);
-
-    // The administrator must explicitly change the text format.
-    $this->drupalLogin($this->adminUser);
-    $edit = [
-      'translation[config_names][config_translation_test.content][content][format]' => 'full_html',
-    ];
-    $this->drupalGet($translation_page_url);
-    $this->submitForm($edit, 'Save translation');
-    $expected = [
-      'value' => '<p><strong>Hello World</strong> - FR</p>',
-      'format' => 'full_html',
-    ];
-    $actual = $config_factory
-      ->get('config_translation_test.content')
-      ->get('content');
-    $this->assertEquals($expected, $actual);
-  }
-
-  /**
-   * Tests field translation for node fields.
-   */
-  public function testNodeFieldTranslation() {
-    NodeType::create(['type' => 'article', 'name' => 'Article'])->save();
-
-    $field_name = 'translatable_field';
-    $field_storage = FieldStorageConfig::create([
-      'field_name' => $field_name,
-      'entity_type' => 'node',
-      'type' => 'text',
-    ]);
-
-    $field_storage->setSetting('translatable_storage_setting', 'translatable_storage_setting');
-    $field_storage->save();
-    $field = FieldConfig::create([
-      'field_name' => $field_name,
-      'entity_type' => 'node',
-      'bundle' => 'article',
-    ]);
-    $field->save();
-
-    $this->drupalLogin($this->translatorUser);
-
-    $this->drupalGet("/entity_test/structure/article/fields/node.article.$field_name/translate");
-    $this->clickLink('Add');
-
-    $form_values = [
-      'translation[config_names][field.field.node.article.translatable_field][description]' => 'FR Help text.',
-      'translation[config_names][field.field.node.article.translatable_field][label]' => 'FR label',
-    ];
-    $this->submitForm($form_values, 'Save translation');
-    $this->assertSession()->pageTextContains('Successfully saved French translation.');
-
-    // Check that the translations are saved.
-    $this->clickLink('Add');
-    $this->assertSession()->responseContains('FR label');
-  }
-
-  /**
-   * Test translation save confirmation message.
-   */
-  public function testMenuTranslationWithoutChange() {
-    $this->drupalLogin($this->adminUser);
-    $this->drupalGet('admin/structure/menu/manage/main/translate/tyv/add');
-    $this->submitForm([], 'Save translation');
-    $this->assertSession()->pageTextContains('Tuvan translation was not added. To add a translation, you must modify the configuration.');
-
-    $this->drupalGet('admin/structure/menu/manage/main/translate/tyv/add');
-    $edit = [
-      'translation[config_names][system.menu.main][label]' => 'Main navigation Translation',
-      'translation[config_names][system.menu.main][description]' => 'Site section links Translation',
-    ];
-    $this->submitForm($edit, 'Save translation');
-    $this->assertSession()->pageTextContains('Successfully saved Tuvan translation.');
-  }
-
-  /**
-   * Gets translation from locale storage.
-   *
-   * @param $config_name
-   *   Configuration object.
-   * @param $key
-   *   Translation configuration field key.
-   * @param $langcode
-   *   String language code to load translation.
-   *
-   * @return bool|mixed
-   *   Returns translation if exists, FALSE otherwise.
-   */
-  protected function getTranslation($config_name, $key, $langcode) {
-    $settings_locations = $this->localeStorage->getLocations(['type' => 'configuration', 'name' => $config_name]);
-    $this->assertNotEmpty($settings_locations, "$config_name should have configuration locations.");
-
-    if (!empty($settings_locations)) {
-      $source = $this->container->get('config.factory')->get($config_name)->get($key);
-      $source_string = $this->localeStorage->findString(['source' => $source, 'type' => 'configuration']);
-      $this->assertNotEmpty($source_string, "$config_name.$key should have a source string.");
-
-      if (!empty($source_string)) {
-        $conditions = [
-          'lid' => $source_string->lid,
-          'language' => $langcode,
-        ];
-        $translations = $this->localeStorage->getTranslations($conditions + ['translated' => TRUE]);
-        return reset($translations);
-      }
-    }
-    return FALSE;
-  }
-
-  /**
-   * Sets site name and slogan for default language, helps in tests.
-   *
-   * @param string $site_name
-   *   The site name.
-   * @param string $site_slogan
-   *   The site slogan.
-   */
-  protected function setSiteInformation($site_name, $site_slogan) {
-    $edit = [
-      'site_name' => $site_name,
-      'site_slogan' => $site_slogan,
-    ];
-    $this->drupalGet('admin/config/system/site-information');
-    $this->submitForm($edit, 'Save configuration');
-    $this->assertSession()->pageTextContains('The configuration options have been saved.');
-  }
-
-  /**
-   * Asserts that a textarea with a given ID has been disabled from editing.
-   *
-   * @param string $id
-   *   The HTML ID of the textarea.
-   *
-   * @internal
-   */
-  protected function assertDisabledTextarea(string $id): void {
-    $textarea = $this->assertSession()->fieldDisabled($id);
-    $this->assertSame('textarea', $textarea->getTagName());
-    $this->assertSame('This field has been disabled because you do not have sufficient permissions to edit it.', $textarea->getText());
-    // Make sure the text format select is not shown.
-    $select_id = str_replace('value', 'format--2', $id);
-    $xpath = $this->assertSession()->buildXPathQuery('//select[@id=:id]', [':id' => $select_id]);
-    $this->assertSession()->elementNotExists('xpath', $xpath);
-  }
-
-  /**
-   * Helper function that returns a .po file with a given number of plural forms.
-   */
-  public function getPoFile($plurals) {
-    $po_file = [];
-
-    $po_file[1] = <<< EOF
-msgid ""
-msgstr ""
-"Project-Id-Version: Drupal 8\\n"
-"MIME-Version: 1.0\\n"
-"Content-Type: text/plain; charset=UTF-8\\n"
-"Content-Transfer-Encoding: 8bit\\n"
-"Plural-Forms: nplurals=1; plural=0;\\n"
-EOF;
-
-    $po_file[2] = <<< EOF
-msgid ""
-msgstr ""
-"Project-Id-Version: Drupal 8\\n"
-"MIME-Version: 1.0\\n"
-"Content-Type: text/plain; charset=UTF-8\\n"
-"Content-Transfer-Encoding: 8bit\\n"
-"Plural-Forms: nplurals=2; plural=(n>1);\\n"
-EOF;
-
-    $po_file[4] = <<< EOF
-msgid ""
-msgstr ""
-"Project-Id-Version: Drupal 8\\n"
-"MIME-Version: 1.0\\n"
-"Content-Type: text/plain; charset=UTF-8\\n"
-"Content-Transfer-Encoding: 8bit\\n"
-"Plural-Forms: nplurals=4; plural=(((n%100)==1)?(0):(((n%100)==2)?(1):((((n%100)==3)||((n%100)==4))?(2):3)));\\n"
-EOF;
-
-    return $po_file[$plurals];
-  }
-
 }
diff --git a/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiTestBase.php b/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..4b25616a558d3220184a552a34c07dd33a05aa9b
--- /dev/null
+++ b/core/modules/config_translation/tests/src/Functional/ConfigTranslationUiTestBase.php
@@ -0,0 +1,232 @@
+<?php
+
+namespace Drupal\Tests\config_translation\Functional;
+
+use Drupal\Core\Test\AssertMailTrait;
+use Drupal\filter\Entity\FilterFormat;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Translate settings and entities to various languages.
+ */
+abstract class ConfigTranslationUiTestBase extends BrowserTestBase {
+
+  use AssertMailTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Languages to enable.
+   *
+   * @var array
+   */
+  protected $langcodes = ['fr', 'ta', 'tyv'];
+
+  /**
+   * Administrator user for tests.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * Translator user for tests.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $translatorUser;
+
+  /**
+   * String translation storage object.
+   *
+   * @var \Drupal\locale\StringStorageInterface
+   */
+  protected $localeStorage;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  protected static $modules = [
+    'block',
+    'config_translation',
+    'config_translation_test',
+    'contact',
+    'contact_test',
+    'contextual',
+    'entity_test',
+    'field_test',
+    'field_ui',
+    'filter',
+    'filter_test',
+    'node',
+    'views',
+    'views_ui',
+    'menu_ui',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $translator_permissions = [
+      'translate configuration',
+    ];
+
+    /** @var \Drupal\filter\FilterFormatInterface $filter_test_format */
+    $filter_test_format = FilterFormat::load('filter_test');
+    /** @var \Drupal\filter\FilterFormatInterface $filtered_html_format */
+    $filtered_html_format = FilterFormat::load('filtered_html');
+    /** @var \Drupal\filter\FilterFormatInterface $full_html_format */
+    $full_html_format = FilterFormat::load('full_html');
+
+    $admin_permissions = array_merge(
+      $translator_permissions,
+      [
+        'administer languages',
+        'administer site configuration',
+        'link to any page',
+        'administer contact forms',
+        'administer filters',
+        $filtered_html_format->getPermissionName(),
+        $full_html_format->getPermissionName(),
+        $filter_test_format->getPermissionName(),
+        'access site-wide contact form',
+        'access contextual links',
+        'administer views',
+        'administer account settings',
+        'administer themes',
+        'bypass node access',
+        'administer content types',
+        'translate interface',
+      ]
+    );
+    // Create and log in user.
+    $this->translatorUser = $this->drupalCreateUser($translator_permissions);
+    $this->adminUser = $this->drupalCreateUser($admin_permissions);
+
+    // Add languages.
+    foreach ($this->langcodes as $langcode) {
+      ConfigurableLanguage::createFromLangcode($langcode)->save();
+    }
+    $this->localeStorage = $this->container->get('locale.storage');
+    $this->drupalPlaceBlock('local_tasks_block');
+    $this->drupalPlaceBlock('page_title_block');
+  }
+
+  /**
+   * Gets translation from locale storage.
+   *
+   * @param $config_name
+   *   Configuration object.
+   * @param $key
+   *   Translation configuration field key.
+   * @param $langcode
+   *   String language code to load translation.
+   *
+   * @return bool|mixed
+   *   Returns translation if exists, FALSE otherwise.
+   */
+  protected function getTranslation($config_name, $key, $langcode) {
+    $settings_locations = $this->localeStorage->getLocations(['type' => 'configuration', 'name' => $config_name]);
+    $this->assertNotEmpty($settings_locations, "$config_name should have configuration locations.");
+
+    if ($settings_locations) {
+      $source = $this->container->get('config.factory')->get($config_name)->get($key);
+      $source_string = $this->localeStorage->findString(['source' => $source, 'type' => 'configuration']);
+      $this->assertNotEmpty($source_string, "$config_name.$key should have a source string.");
+
+      if ($source_string) {
+        $conditions = [
+          'lid' => $source_string->lid,
+          'language' => $langcode,
+        ];
+        $translations = $this->localeStorage->getTranslations($conditions + ['translated' => TRUE]);
+        return reset($translations);
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Sets site name and slogan for default language, helps in tests.
+   *
+   * @param string $site_name
+   *   The site name.
+   * @param string $site_slogan
+   *   The site slogan.
+   */
+  protected function setSiteInformation($site_name, $site_slogan) {
+    $edit = [
+      'site_name' => $site_name,
+      'site_slogan' => $site_slogan,
+    ];
+    $this->drupalGet('admin/config/system/site-information');
+    $this->submitForm($edit, 'Save configuration');
+    $this->assertSession()->pageTextContains('The configuration options have been saved.');
+  }
+
+  /**
+   * Asserts that a textarea with a given ID has been disabled from editing.
+   *
+   * @param string $id
+   *   The HTML ID of the textarea.
+   *
+   * @internal
+   */
+  protected function assertDisabledTextarea(string $id): void {
+    $textarea = $this->assertSession()->fieldDisabled($id);
+    $this->assertSame('textarea', $textarea->getTagName());
+    $this->assertSame('This field has been disabled because you do not have sufficient permissions to edit it.', $textarea->getText());
+    // Make sure the text format select is not shown.
+    $select_id = str_replace('value', 'format--2', $id);
+    $xpath = $this->assertSession()->buildXPathQuery('//select[@id=:id]', [':id' => $select_id]);
+    $this->assertSession()->elementNotExists('xpath', $xpath);
+  }
+
+  /**
+   * Helper function that returns a .po file with a given number of plural forms.
+   */
+  public function getPoFile($plurals) {
+    $po_file = [];
+
+    $po_file[1] = <<< EOF
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 8\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=1; plural=0;\\n"
+EOF;
+
+    $po_file[2] = <<< EOF
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 8\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=2; plural=(n>1);\\n"
+EOF;
+
+    $po_file[4] = <<< EOF
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 8\\n"
+"MIME-Version: 1.0\\n"
+"Content-Type: text/plain; charset=UTF-8\\n"
+"Content-Transfer-Encoding: 8bit\\n"
+"Plural-Forms: nplurals=4; plural=(((n%100)==1)?(0):(((n%100)==2)?(1):((((n%100)==3)||((n%100)==4))?(2):3)));\\n"
+EOF;
+
+    return $po_file[$plurals];
+  }
+
+}
diff --git a/core/modules/config_translation/tests/src/Functional/GenericTest.php b/core/modules/config_translation/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9fbe71dcbe3b0d92c36365b38de74025891e58be
--- /dev/null
+++ b/core/modules/config_translation/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\config_translation\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for config_translation.
+ *
+ * @group config_translation
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/contact/tests/src/Functional/GenericTest.php b/core/modules/contact/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4e2be63a5255dbe1bc4c15dffbee7f159a04afc0
--- /dev/null
+++ b/core/modules/contact/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\contact\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for contact.
+ *
+ * @group contact
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php
index fabce0090f52edd91c4d07173777a7de30029890..31d7856194a37f70283c0d9910d63919b935cbbf 100644
--- a/core/modules/content_moderation/src/Entity/ContentModerationState.php
+++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
@@ -161,8 +161,10 @@ public static function loadFromModeratedEntity(EntityInterface $entity) {
    * {@inheritdoc}
    */
   public function save() {
-    $related_entity = \Drupal::entityTypeManager()
-      ->getStorage($this->content_entity_type_id->value)
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = \Drupal::entityTypeManager()
+      ->getStorage($this->content_entity_type_id->value);
+    $related_entity = $storage
       ->loadRevision($this->content_entity_revision_id->value);
     if ($related_entity instanceof TranslatableInterface) {
       $related_entity = $related_entity->getTranslation($this->activeLangcode);
diff --git a/core/modules/content_moderation/src/ModerationInformation.php b/core/modules/content_moderation/src/ModerationInformation.php
index a41b154f279d1086f099ebb64d56659e783c5494..78d0dc6014acfca79622a8ad9349302c6b582b28 100644
--- a/core/modules/content_moderation/src/ModerationInformation.php
+++ b/core/modules/content_moderation/src/ModerationInformation.php
@@ -120,7 +120,7 @@ public function getAffectedRevisionTranslation(ContentEntityInterface $entity) {
   public function hasPendingRevision(ContentEntityInterface $entity) {
     $result = FALSE;
     if ($this->isModeratedEntity($entity)) {
-      /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
       $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
       $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId());
       $default_revision_id = $entity->isDefaultRevision() && !$entity->isNewRevision() && ($revision_id = $entity->getRevisionId()) ?
@@ -215,8 +215,10 @@ public function getOriginalState(ContentEntityInterface $entity) {
     $state = NULL;
     $workflow_type = $this->getWorkflowForEntity($entity)->getTypePlugin();
     if (!$entity->isNew() && !$this->isFirstTimeModeration($entity)) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+      $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
       /** @var \Drupal\Core\Entity\ContentEntityInterface $original_entity */
-      $original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadRevision($entity->getLoadedRevisionId());
+      $original_entity = $storage->loadRevision($entity->getLoadedRevisionId());
       if (!$entity->isDefaultTranslation() && $original_entity->hasTranslation($entity->language()->getId())) {
         $original_entity = $original_entity->getTranslation($entity->language()->getId());
       }
@@ -240,6 +242,7 @@ public function getOriginalState(ContentEntityInterface $entity) {
    *   TRUE if this is the entity's first time being moderated, FALSE otherwise.
    */
   protected function isFirstTimeModeration(ContentEntityInterface $entity) {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
     $original_entity = $storage->loadRevision($storage->getLatestRevisionId($entity->id()));
 
diff --git a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
index f5ae4c6be3ef413deac68580f194214bb88c51b4..e641dbb8d5b6da14e75af1faaf68b5c3375ed8c5 100644
--- a/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
+++ b/core/modules/content_moderation/src/Plugin/Field/FieldWidget/ModerationStateWidget.php
@@ -125,8 +125,10 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     // The moderation state of the saved revision will be used to display the
     // current state as well determine the appropriate transitions.
     if (!$entity->isNew()) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+      $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
       /** @var \Drupal\Core\Entity\ContentEntityInterface $original_entity */
-      $original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadRevision($entity->getLoadedRevisionId());
+      $original_entity = $storage->loadRevision($entity->getLoadedRevisionId());
       if (!$entity->isDefaultTranslation() && $original_entity->hasTranslation($entity->language()->getId())) {
         $original_entity = $original_entity->getTranslation($entity->language()->getId());
       }
diff --git a/core/modules/content_moderation/tests/src/Functional/GenericTest.php b/core/modules/content_moderation/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..25cf6df650e6b4176518cc86333af1d2cb36e9ad
--- /dev/null
+++ b/core/modules/content_moderation/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\content_moderation\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for content_moderation.
+ *
+ * @group content_moderation
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/content_moderation/tests/src/Functional/ModeratedContentLocalTaskTest.php b/core/modules/content_moderation/tests/src/Functional/ModeratedContentLocalTaskTest.php
index 64cc55f1b02e2bfdf6a107feb77cabd1410a2d3d..7f1bd1acb5cb8b4ce4565e0799931e740faf5809 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModeratedContentLocalTaskTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModeratedContentLocalTaskTest.php
@@ -64,8 +64,7 @@ public function testModeratedContentLocalTask() {
     // Verify the moderated content local task does not exist without the node
     // module installed.
     $this->drupalGet('admin/content');
-    $this->assertSession()->statusCodeEquals(200);
-    $this->assertSession()->linkNotExists('Moderated content');
+    $this->assertSession()->statusCodeEquals(403);
   }
 
 }
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
index 3d7ad6533eebf6220da4fbac3a1eed0645c2d329..8d08a745ad123b0d88200e2b84224eeca72a9a12 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
@@ -9,6 +9,7 @@
  * Tests the moderation form, specifically on nodes.
  *
  * @group content_moderation
+ * @group #slow
  */
 class ModerationFormTest extends ModerationStateTestBase {
 
diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
index 90bad5caf69467e8f06cbef45d1b5b702dd0767b..6317a1f4ec5dc67008b5dec117fe9d11aa7a39c2 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
@@ -150,6 +150,7 @@ public function testBasicModeration($entity_type_id) {
     $entity->save();
 
     // Revert to the previous (published) revision.
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entity_storage */
     $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
     $previous_revision = $entity_storage->loadRevision(4);
     $previous_revision->isDefaultRevision(TRUE);
@@ -231,6 +232,7 @@ public function testContentModerationStateRevisionDataRemoval($entity_type_id) {
     // Delete the second revision and check that its content moderation state is
     // removed as well, while the content moderation states for revisions 1 and
     // 3 are kept in place.
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entity_storage */
     $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
     $entity_storage->deleteRevision($revision_2->getRevisionId());
 
@@ -253,6 +255,7 @@ public function testContentModerationStatePendingRevisionDataRemoval($entity_typ
     $content_moderation_state = ContentModerationState::loadFromModeratedEntity($entity);
     $this->assertNotEmpty($content_moderation_state);
 
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entity_storage */
     $entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
     $entity_storage->deleteRevision($entity->getRevisionId());
 
@@ -264,6 +267,7 @@ public function testContentModerationStatePendingRevisionDataRemoval($entity_typ
    * Tests removal of content moderation state entities for preexisting content.
    */
   public function testExistingContentModerationStateDataRemoval() {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->entityTypeManager->getStorage('entity_test_mulrevpub');
 
     $entity = $this->createEntity('entity_test_mulrevpub', 'published', FALSE);
@@ -777,6 +781,7 @@ protected function createEntity($entity_type_id, $moderation_state = 'published'
    *   The reloaded entity.
    */
   protected function reloadEntity(EntityInterface $entity, $revision_id = FALSE) {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
     $storage->resetCache([$entity->id()]);
     if ($revision_id) {
diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationSyncingTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationSyncingTest.php
index 4a14164585102d073dd36d06c29aa474bbf5ed58..9e0dfa5db41e6f68f7cc52dd08f72bf6e9f9ae78 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationSyncingTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationSyncingTest.php
@@ -115,6 +115,7 @@ public function testMultipleRevisionStateChangedDuringSync() {
    * Tests modifying a previous revision during a sync.
    */
   public function testUpdatingPreviousRevisionDuringSync() {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->container->get('entity_type.manager')->getStorage('entity_test_mulrevpub');
 
     $entity = EntityTestMulRevPub::create([
@@ -142,6 +143,7 @@ public function testUpdatingPreviousRevisionDuringSync() {
    * Tests a moderation state changed on a previous revision during a sync.
    */
   public function testStateChangedPreviousRevisionDuringSync() {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->container->get('entity_type.manager')->getStorage('entity_test_mulrevpub');
 
     $entity = EntityTestMulRevPub::create([
@@ -188,6 +190,7 @@ public function testStateChangedPreviousRevisionDuringSync() {
    *   An array of revision names.
    */
   protected function getAllRevisionNames(EntityTestMulRevPub $entity) {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->container->get('entity_type.manager')->getStorage('entity_test_mulrevpub');
     return array_map(function ($revision_id) use ($storage) {
       return $storage->loadRevision($revision_id)->name->value;
diff --git a/core/modules/content_moderation/tests/src/Kernel/WorkspacesContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/WorkspacesContentModerationStateTest.php
index b92be50c5e0b07b3d1bf7f83e493e63bb4823e94..6589ec00f199821a3540aaca3a408bbe449bdc95 100644
--- a/core/modules/content_moderation/tests/src/Kernel/WorkspacesContentModerationStateTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/WorkspacesContentModerationStateTest.php
@@ -17,6 +17,7 @@
  *
  * @group content_moderation
  * @group workspaces
+ * @group #slow
  */
 class WorkspacesContentModerationStateTest extends ContentModerationStateTest {
 
diff --git a/core/modules/content_translation/migrations/d7_taxonomy_term_entity_translation.yml b/core/modules/content_translation/migrations/d7_taxonomy_term_entity_translation.yml
index f4ab6731bfa20e1318a96391557e8c4c3181e80a..a156ab68de67e998195b4a745787deea386a01df 100644
--- a/core/modules/content_translation/migrations/d7_taxonomy_term_entity_translation.yml
+++ b/core/modules/content_translation/migrations/d7_taxonomy_term_entity_translation.yml
@@ -20,7 +20,6 @@ process:
   content_translation_uid: uid
   content_translation_created: created
   changed: changed
-  forum_container: is_container
 destination:
   plugin: entity:taxonomy_term
   translations: true
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index d3dad8ee8044882f7667383ec728404af0946652..b4911b60afa37d7e0d2150f0a021ec46b4446377 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -541,10 +541,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
         '#type' => 'textfield',
         '#title' => t('Authored on'),
         '#maxlength' => 25,
-        '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', [
-          '%time' => $this->dateFormatter->format(REQUEST_TIME, 'custom', 'Y-m-d H:i:s O'),
-          '%timezone' => $this->dateFormatter->format(REQUEST_TIME, 'custom', 'O'),
-        ]),
+        '#description' => t('Leave blank to use the time of form submission.'),
         '#default_value' => $new_translation || !$date ? '' : $this->dateFormatter->format($date, 'custom', 'Y-m-d H:i:s O'),
       ];
 
diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php
index 7cbc51451808dcc80b0294f5545ec723eece802e..b906d49803981fe8e70b9eaaa1bcf42721580bf1 100644
--- a/core/modules/content_translation/src/Controller/ContentTranslationController.php
+++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
@@ -68,6 +68,33 @@ public static function create(ContainerInterface $container) {
    */
   public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) {
     $source_langcode = $source->getId();
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+    $storage = $this->entityTypeManager()->getStorage($entity->getEntityTypeId());
+
+    // Once translations from the default revision are added, there may be
+    // additional draft translations that don't exist in the default revision.
+    // Add those translations too so that they aren't deleted when the new
+    // translation is saved.
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
+    $default_revision = $storage->load($entity->id());
+    // Check the entity isn't missing any translations.
+    $languages = $this->languageManager()->getLanguages();
+    foreach ($languages as $language) {
+      $langcode = $language->getId();
+      if ($entity->hasTranslation($langcode) || $target->getId() === $langcode) {
+        continue;
+      }
+      $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
+      if ($latest_revision_id) {
+        if ($default_revision->hasTranslation($langcode)) {
+          $existing_translation = $default_revision->getTranslation($langcode);
+          $existing_translation->setNewRevision(FALSE);
+          $existing_translation->isDefaultRevision(FALSE);
+          $existing_translation->setRevisionTranslationAffected(FALSE);
+          $entity->addTranslation($langcode, $existing_translation->toArray());
+        }
+      }
+    }
     /** @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */
     $source_translation = $entity->getTranslation($source_langcode);
     $target_translation = $entity->addTranslation($target->getId(), $source_translation->toArray());
diff --git a/core/modules/content_translation/src/FieldTranslationSynchronizer.php b/core/modules/content_translation/src/FieldTranslationSynchronizer.php
index 336909d2a7e89d6f00cdd95a2746ff6189429501..3719b0a5cbb43f6feefd04e6fb132e49e8b5de3e 100644
--- a/core/modules/content_translation/src/FieldTranslationSynchronizer.php
+++ b/core/modules/content_translation/src/FieldTranslationSynchronizer.php
@@ -190,6 +190,7 @@ public function synchronizeFields(ContentEntityInterface $entity, $sync_langcode
    */
   protected function getOriginalEntity(ContentEntityInterface $entity) {
     if (!isset($entity->original)) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
       $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
       $original = $entity->isDefaultRevision() ? $storage->loadUnchanged($entity->id()) : $storage->loadRevision($entity->getLoadedRevisionId());
     }
diff --git a/core/modules/content_translation/src/Plugin/Validation/Constraint/ContentTranslationSynchronizedFieldsConstraintValidator.php b/core/modules/content_translation/src/Plugin/Validation/Constraint/ContentTranslationSynchronizedFieldsConstraintValidator.php
index 127b6452b2266667e497ce2fd748afd94a86369d..e52a18081064ddcdcd48777a401801253f183803 100644
--- a/core/modules/content_translation/src/Plugin/Validation/Constraint/ContentTranslationSynchronizedFieldsConstraintValidator.php
+++ b/core/modules/content_translation/src/Plugin/Validation/Constraint/ContentTranslationSynchronizedFieldsConstraintValidator.php
@@ -170,6 +170,7 @@ protected function hasSynchronizedPropertyChanges(ContentEntityInterface $entity
    */
   protected function getOriginalEntity(ContentEntityInterface $entity) {
     if (!isset($entity->original)) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
       $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
       $original = $entity->isDefaultRevision() ? $storage->loadUnchanged($entity->id()) : $storage->loadRevision($entity->getLoadedRevisionId());
     }
diff --git a/core/modules/content_translation/tests/modules/content_translation_test/content_translation_test.module b/core/modules/content_translation/tests/modules/content_translation_test/content_translation_test.module
index 50495a8c799b202413954134d27803e6f8fc89ca..99d14eb19f5de205dbe2bf1c27a1dcd8b5bb878a 100644
--- a/core/modules/content_translation/tests/modules/content_translation_test/content_translation_test.module
+++ b/core/modules/content_translation/tests/modules/content_translation_test/content_translation_test.module
@@ -68,3 +68,10 @@ function content_translation_test_form_node_form_alter(&$form, FormStateInterfac
 function content_translation_test_form_node_form_submit($form, FormStateInterface $form_state) {
   \Drupal::state()->set('test_field_only_en_fr', $form_state->getValue('test_field_only_en_fr'));
 }
+
+/**
+ * Implements hook_entity_translation_delete().
+ */
+function content_translation_test_entity_translation_delete(EntityInterface $translation) {
+  \Drupal::state()->set('content_translation_test.translation_deleted', TRUE);
+}
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationDisableSettingTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationDisableSettingTest.php
index 4c56eecf08d7f87a10a36beae00d91d4e9f32155..33c45440af5916e8ee77f58e5b1ae0ea28f8a4b9 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTranslationDisableSettingTest.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationDisableSettingTest.php
@@ -7,6 +7,8 @@
 /**
  * Test disabling content translation module.
  *
+ * @covers \Drupal\language\Form\ContentLanguageSettingsForm
+ * @covers ::_content_translation_form_language_content_settings_form_alter
  * @group content_translation
  */
 class ContentTranslationDisableSettingTest extends BrowserTestBase {
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationEnableTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationEnableTest.php
index 7c3ebcdcb75ce3bcb6845217642cb09397e6683a..2990746f85d9c7d84a67b9cd0a4cfad1f9acfc92 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTranslationEnableTest.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationEnableTest.php
@@ -7,6 +7,8 @@
 /**
  * Test enabling content translation module.
  *
+ * @covers \Drupal\language\Form\ContentLanguageSettingsForm
+ * @covers ::_content_translation_form_language_content_settings_form_alter
  * @group content_translation
  */
 class ContentTranslationEnableTest extends BrowserTestBase {
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationNewTranslationWithExistingRevisionsTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationNewTranslationWithExistingRevisionsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e11056f56c56536e8fbb033f0820075287d9ff6f
--- /dev/null
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationNewTranslationWithExistingRevisionsTest.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace Drupal\Tests\content_translation\Functional;
+
+use Drupal\Core\Url;
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests that new translations do not delete existing ones.
+ *
+ * @group content_translation
+ */
+class ContentTranslationNewTranslationWithExistingRevisionsTest extends ContentTranslationPendingRevisionTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'content_moderation',
+    'content_translation',
+    'content_translation_test',
+    'language',
+    'node',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->enableContentModeration();
+  }
+
+  /**
+   * Tests a translation with a draft is not deleted.
+   */
+  public function testDraftTranslationIsNotDeleted() {
+    $this->drupalLogin($this->translator);
+
+    // Create a test node.
+    $values = [
+      'title' => "Test EN",
+      'moderation_state' => 'published',
+    ];
+    $id = $this->createEntity($values, 'en');
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $this->storage->load($id);
+
+    // Add a published translation.
+    $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
+      [
+        $entity->getEntityTypeId() => $id,
+        'source' => 'en',
+        'target' => 'it',
+      ],
+      [
+        'language' => ConfigurableLanguage::load('it'),
+        'absolute' => FALSE,
+      ]
+    );
+    $this->drupalGet($add_translation_url);
+    $edit = [
+      'title[0][value]' => "Test IT",
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->submitForm($edit, 'Save (this translation)');
+    $it_revision = $this->loadRevisionTranslation($entity, 'it');
+
+    // Add a draft translation.
+    $this->drupalGet($this->getEditUrl($it_revision));
+    $edit = [
+      'title[0][value]' => "Test IT 2",
+      'moderation_state[0][state]' => 'draft',
+    ];
+    $this->submitForm($edit, 'Save (this translation)');
+
+    // Add a new draft translation.
+    $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
+      [
+        $entity->getEntityTypeId() => $id,
+        'source' => 'en',
+        'target' => 'fr',
+      ],
+      [
+        'language' => ConfigurableLanguage::load('fr'),
+        'absolute' => FALSE,
+      ]
+    );
+    $this->drupalGet($add_translation_url);
+    $edit = [
+      'title[0][value]' => "Test FR",
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->submitForm($edit, 'Save (this translation)');
+    // Check the first translation still exists.
+    $entity = $this->storage->loadUnchanged($id);
+    $this->assertTrue($entity->hasTranslation('it'));
+  }
+
+  /**
+   * Test translation delete hooks are not invoked.
+   */
+  public function testCreatingNewDraftDoesNotInvokeDeleteHook() {
+    $this->drupalLogin($this->translator);
+
+    // Create a test node.
+    $values = [
+      'title' => "Test EN",
+      'moderation_state' => 'published',
+    ];
+    $id = $this->createEntity($values, 'en');
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $this->storage->load($id);
+
+    // Add a published translation.
+    $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
+      [
+        $entity->getEntityTypeId() => $id,
+        'source' => 'en',
+        'target' => 'it',
+      ],
+      [
+        'language' => ConfigurableLanguage::load('it'),
+        'absolute' => FALSE,
+      ]
+    );
+    $this->drupalGet($add_translation_url);
+    $edit = [
+      'title[0][value]' => "Test IT",
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->submitForm($edit, 'Save (this translation)');
+    $it_revision = $this->loadRevisionTranslation($entity, 'it');
+
+    // Add a draft translation.
+    $this->drupalGet($this->getEditUrl($it_revision));
+    $edit = [
+      'title[0][value]' => "Test IT 2",
+      'moderation_state[0][state]' => 'draft',
+    ];
+    $this->submitForm($edit, 'Save (this translation)');
+    // Add a new draft translation.
+    $add_translation_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add",
+      [
+        $entity->getEntityTypeId() => $id,
+        'source' => 'en',
+        'target' => 'fr',
+      ],
+      [
+        'language' => ConfigurableLanguage::load('fr'),
+        'absolute' => FALSE,
+      ]
+    );
+    $this->drupalGet($add_translation_url);
+    $edit = [
+      'title[0][value]' => "Test FR",
+      'moderation_state[0][state]' => 'draft',
+    ];
+    $this->submitForm($edit, 'Save (this translation)');
+    // If the translation delete hook was incorrectly invoked, the state
+    // variable would be set.
+    $this->assertNull($this->container->get('state')->get('content_translation_test.translation_deleted'));
+  }
+
+}
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationSettingsTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationSettingsTest.php
index cb885c0fb37d7afc5d4a5801105ecb7adba57f90..a2d9b85d9ea36eb2d8686f8e4ff9e8fd9e66dffa 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTranslationSettingsTest.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationSettingsTest.php
@@ -15,6 +15,8 @@
 /**
  * Tests the content translation settings UI.
  *
+ * @covers \Drupal\language\Form\ContentLanguageSettingsForm
+ * @covers ::_content_translation_form_language_content_settings_form_alter
  * @group content_translation
  */
 class ContentTranslationSettingsTest extends BrowserTestBase {
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationUISkipTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationUISkipTest.php
index 960e0e06a1bc11262cb73d970013b6be26880b30..c579c26525716e2b92e6fdb11e007f6d2530f36a 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTranslationUISkipTest.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationUISkipTest.php
@@ -7,6 +7,8 @@
 /**
  * Tests the content translation UI check skip.
  *
+ * @covers \Drupal\language\Form\ContentLanguageSettingsForm
+ * @covers ::_content_translation_form_language_content_settings_form_alter
  * @group content_translation
  */
 class ContentTranslationUISkipTest extends BrowserTestBase {
diff --git a/core/modules/content_translation/tests/src/Functional/GenericTest.php b/core/modules/content_translation/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..25efc14fa1a95a12e0d1bd511e146df941154acd
--- /dev/null
+++ b/core/modules/content_translation/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\content_translation\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for content_translation.
+ *
+ * @group content_translation
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/contextual/tests/src/Functional/GenericTest.php b/core/modules/contextual/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0387c894d23b0df7292a9f4615d0c4700bb363b1
--- /dev/null
+++ b/core/modules/contextual/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\contextual\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for contextual.
+ *
+ * @group contextual
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_argument_datetime.yml b/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_argument_datetime.yml
index 91a10b8b536c25af53171af7478a52ac47f62cea..8b0681e9b40713789a7dcfce147c2332f88a7724 100644
--- a/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_argument_datetime.yml
+++ b/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_argument_datetime.yml
@@ -2,7 +2,7 @@ langcode: und
 status: true
 dependencies: {  }
 id: test_argument_datetime
-label: ''
+label: test_argument_datetime
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_filter_datetime.yml b/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_filter_datetime.yml
index ed7169b6ee0bf2105579631528f43e08d40d9c89..f8f0eee137ef732a537203abab80551447460080 100644
--- a/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_filter_datetime.yml
+++ b/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_filter_datetime.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_filter_datetime
-label: ''
+label: test_filter_datetime
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_sort_datetime.yml b/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_sort_datetime.yml
index 985d21cd20f461904126622a81dabca9d6b62736..9a093401124dcb54ce873cf3eeb8787db2d2aedf 100644
--- a/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_sort_datetime.yml
+++ b/core/modules/datetime/tests/modules/datetime_test/test_views/views.view.test_sort_datetime.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_sort_datetime
-label: ''
+label: test_sort_datetime
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
index 67d8c328da62cc2d353484cdf79c4657664febfa..ca201a398f72376e507bca26ac4e4f15f17969fa 100644
--- a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
+++ b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php
@@ -393,12 +393,9 @@ public function testDatetimeField() {
     $form_state = new FormState();
     \Drupal::formBuilder()->submitForm($form, $form_state);
     $errors = $form_state->getErrors();
-    $arguments = $errors["{$field_name}][0][value"]->getArguments();
-    $expected_error_message = new FormattableMarkup('The %field date is required. Enter a date in the format %format.', ['%field' => $field_label, '%format' => $arguments['%format']]);
+    $expected_error_message = new FormattableMarkup('The %field date is required.', ['%field' => $field_label]);
     $actual_error_message = $errors["{$field_name}][0][value"]->__toString();
     $this->assertEquals($expected_error_message->__toString(), $actual_error_message);
-    // Verify the format value is in the "YYYY-MM-DD HH:MM:SS" format.
-    $this->assertMatchesRegularExpression('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $arguments['%format']);
   }
 
   /**
diff --git a/core/modules/datetime/tests/src/Functional/GenericTest.php b/core/modules/datetime/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..98d85e42f8aa9b4bf10a296f6d8be6ebc5c198cb
--- /dev/null
+++ b/core/modules/datetime/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\datetime\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for datetime.
+ *
+ * @group datetime
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/datetime_range/css/datetime_range.icon.theme.css b/core/modules/datetime_range/css/datetime_range.icon.theme.css
index 786ef59a15e4c629d6a44e2e65e0679288fd1ebf..028163f29e1c58c1995040d0adca0f9b5f7c73eb 100644
--- a/core/modules/datetime_range/css/datetime_range.icon.theme.css
+++ b/core/modules/datetime_range/css/datetime_range.icon.theme.css
@@ -5,5 +5,5 @@
  * @preserve
  */
 .field-icon-daterange {
-  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='none'%3e  %3cpath d='M15 2h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h4V0h2v2h6V0h2v2ZM2 8v10h16V8H2Zm2 2h2v2H4v-2Zm5 0h2v2H9v-2Zm5 0h2v2h-2v-2Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m27 3.6h7.2c.9941 0 1.8.8059 1.8 1.8v28.8c0 .9941-.8059 1.8-1.8 1.8h-32.4c-.994104 0-1.8-.8059-1.8-1.8v-28.8c0-.9941.805896-1.8 1.8-1.8h7.2v-3.6h3.6v3.6h10.8v-3.6h3.6zm-23.4 10.8v18h28.8v-18zm3.6 3.6h3.6v3.6h-3.6zm9 0h3.6v3.6h-3.6zm9 0h3.6v3.6h-3.6z' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/datetime_range/datetime_range.module b/core/modules/datetime_range/datetime_range.module
index 65a9e692dff98d4090a48e56e4d29dbda4ffaad3..8c81fb52685c91423ac8f9202e5d5b4597ddf1a0 100644
--- a/core/modules/datetime_range/datetime_range.module
+++ b/core/modules/datetime_range/datetime_range.module
@@ -5,6 +5,7 @@
  * Field hooks to implement a datetime field that stores a start and end date.
  */
 
+use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 
@@ -29,8 +30,10 @@ function datetime_range_help($route_name, RouteMatchInterface $route_match) {
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function datetime_range_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'datetime_range/drupal.datetime_range-icon';
+function datetime_range_field_type_category_info_alter(&$definitions) {
+  // The `datetime_range` field type belongs in the `general` category, so the
+  // libraries need to be attached using an alter hook.
+  $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'datetime_range/drupal.datetime_range-icon';
 }
diff --git a/core/modules/datetime_range/tests/src/Functional/GenericTest.php b/core/modules/datetime_range/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ecab43f5121e26456b5dcafeb4342afc2561246
--- /dev/null
+++ b/core/modules/datetime_range/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\datetime_range\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for datetime_range.
+ *
+ * @group datetime_range
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/dblog/tests/src/Functional/GenericTest.php b/core/modules/dblog/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a05f749ed1ad85aa23cb5293ac2baef8ce6280b
--- /dev/null
+++ b/core/modules/dblog/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\dblog\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for dblog.
+ *
+ * @group dblog
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/dynamic_page_cache/tests/src/Functional/GenericTest.php b/core/modules/dynamic_page_cache/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e965f10d3e590379115b4f72106ab22b048fc205
--- /dev/null
+++ b/core/modules/dynamic_page_cache/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\dynamic_page_cache\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for dynamic_page_cache.
+ *
+ * @group dynamic_page_cache
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/editor/editor.admin.inc b/core/modules/editor/editor.admin.inc
index 6a474689d43986d82dbacd76e794ec14d50b5eaa..b8245e7df1bf57f466b56248a19a5dc492413063 100644
--- a/core/modules/editor/editor.admin.inc
+++ b/core/modules/editor/editor.admin.inc
@@ -7,6 +7,7 @@
 
 use Drupal\Component\Utility\Environment;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\editor\Entity\Editor;
 
 /**
@@ -78,7 +79,7 @@ function editor_image_upload_settings_form(Editor $editor) {
     '#states' => $show_if_image_uploads_enabled,
   ];
 
-  $default_max_size = format_size(Environment::getUploadMaxSize());
+  $default_max_size = ByteSizeMarkup::create(Environment::getUploadMaxSize());
   $form['max_size'] = [
     '#type' => 'textfield',
     '#default_value' => $image_upload['max_size'],
diff --git a/core/modules/editor/src/Form/EditorImageDialog.php b/core/modules/editor/src/Form/EditorImageDialog.php
index 66438c51177e79282f48541a489c8438e055b05f..6e4cf9db2705f096073f2666bf4c03dc305db46c 100644
--- a/core/modules/editor/src/Form/EditorImageDialog.php
+++ b/core/modules/editor/src/Form/EditorImageDialog.php
@@ -20,7 +20,7 @@
  * @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no
  * replacement.
  *
- * @see https://www.drupal.org/project/drupal/issues/3291493
+ * @see https://www.drupal.org/node/3291493
  *
  * @internal
  */
@@ -40,7 +40,7 @@ class EditorImageDialog extends FormBase {
    *   The file storage service.
    */
   public function __construct(EntityStorageInterface $file_storage) {
-    @trigger_error(__NAMESPACE__ . '\EditorImageDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493', E_USER_DEPRECATED);
+    @trigger_error(__NAMESPACE__ . '\EditorImageDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493', E_USER_DEPRECATED);
     $this->fileStorage = $file_storage;
   }
 
diff --git a/core/modules/editor/src/Form/EditorLinkDialog.php b/core/modules/editor/src/Form/EditorLinkDialog.php
index 44644dbfe4d8da8f92ba23449d03e3bbca979cc1..250c967a9c041dec297d64b4491c297270701efe 100644
--- a/core/modules/editor/src/Form/EditorLinkDialog.php
+++ b/core/modules/editor/src/Form/EditorLinkDialog.php
@@ -16,7 +16,7 @@
  * @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no
  * replacement.
  *
- * @see https://www.drupal.org/project/drupal/issues/3291493
+ * @see https://www.drupal.org/node/3291493
  *
  * @internal
  */
@@ -26,7 +26,7 @@ class EditorLinkDialog extends FormBase {
    * Constructs a form object for link dialog.
    */
   public function __construct() {
-    @trigger_error(__NAMESPACE__ . '\EditorLinkDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493', E_USER_DEPRECATED);
+    @trigger_error(__NAMESPACE__ . '\EditorLinkDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493', E_USER_DEPRECATED);
   }
 
   /**
diff --git a/core/modules/editor/tests/src/Functional/GenericTest.php b/core/modules/editor/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bdc6a1a3136a9ffdf6097c82495ceb94df93d4fe
--- /dev/null
+++ b/core/modules/editor/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\editor\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for editor.
+ *
+ * @group editor
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/editor/tests/src/Kernel/EditorDeprecationTest.php b/core/modules/editor/tests/src/Kernel/EditorDeprecationTest.php
index 48923396a47a7e14e14df5305f561e376657d746..eb54a122f10fb78c9689b1dbe491732dd7845406 100644
--- a/core/modules/editor/tests/src/Kernel/EditorDeprecationTest.php
+++ b/core/modules/editor/tests/src/Kernel/EditorDeprecationTest.php
@@ -25,7 +25,7 @@ class EditorDeprecationTest extends KernelTestBase {
    * @see EditorLinkDialog
    */
   public function testEditorLinkDialog(): void {
-    $this->expectDeprecation('Drupal\editor\Form\EditorLinkDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493');
+    $this->expectDeprecation('Drupal\editor\Form\EditorLinkDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493');
     new EditorLinkDialog();
   }
 
@@ -35,7 +35,7 @@ public function testEditorLinkDialog(): void {
    * @see EditorImageDialog
    */
   public function testEditorImageDialog(): void {
-    $this->expectDeprecation('Drupal\editor\Form\EditorImageDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493');
+    $this->expectDeprecation('Drupal\editor\Form\EditorImageDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493');
     new EditorImageDialog($this->createMock('\Drupal\file\FileStorage'));
   }
 
diff --git a/core/modules/editor/tests/src/Kernel/EditorFileReferenceFilterTest.php b/core/modules/editor/tests/src/Kernel/EditorFileReferenceFilterTest.php
index b764eedaef81c92142ff69543edc82413b131793..4c5d2555bc09e41a0df0fe18886ed9ea5228169c 100644
--- a/core/modules/editor/tests/src/Kernel/EditorFileReferenceFilterTest.php
+++ b/core/modules/editor/tests/src/Kernel/EditorFileReferenceFilterTest.php
@@ -86,15 +86,15 @@ public function testEditorFileReferenceFilter() {
     $this->assertSame($input, $output->getProcessedText());
 
     // One data-entity-uuid attribute.
-    $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
-    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
+    $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '">';
+    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '">';
     $output = $test($input);
     $this->assertSame($expected_output, $output->getProcessedText());
     $this->assertEquals($cache_tag, $output->getCacheTags());
 
     // One data-entity-uuid attribute with odd capitalization.
     $input = '<img src="llama.jpg" data-entity-type="file" DATA-entity-UUID =   "' . $uuid . '" />';
-    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
+    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '">';
     $output = $test($input);
     $this->assertSame($expected_output, $output->getProcessedText());
     $this->assertEquals($cache_tag, $output->getCacheTags());
@@ -107,7 +107,7 @@ public function testEditorFileReferenceFilter() {
     $this->assertEquals($cache_tag, $output->getCacheTags());
 
     // One data-entity-uuid attribute with an invalid value.
-    $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="invalid-' . $uuid . '" />';
+    $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="invalid-' . $uuid . '">';
     $output = $test($input);
     $this->assertSame($input, $output->getProcessedText());
     $this->assertEquals([], $output->getCacheTags());
@@ -115,8 +115,8 @@ public function testEditorFileReferenceFilter() {
     // Two different data-entity-uuid attributes.
     $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
     $input .= '<img src="alpaca.jpg" data-entity-type="file" data-entity-uuid="' . $uuid_2 . '" />';
-    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
-    $expected_output .= '<img src="/' . $this->siteDirectory . '/files/alpaca.jpg" data-entity-type="file" data-entity-uuid="' . $uuid_2 . '" />';
+    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '">';
+    $expected_output .= '<img src="/' . $this->siteDirectory . '/files/alpaca.jpg" data-entity-type="file" data-entity-uuid="' . $uuid_2 . '">';
     $output = $test($input);
     $this->assertSame($expected_output, $output->getProcessedText());
     $this->assertEquals(Cache::mergeTags($cache_tag, $cache_tag_2), $output->getCacheTags());
@@ -124,8 +124,8 @@ public function testEditorFileReferenceFilter() {
     // Two identical  data-entity-uuid attributes.
     $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
     $input .= '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
-    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
-    $expected_output .= '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
+    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '">';
+    $expected_output .= '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '">';
     $output = $test($input);
     $this->assertSame($expected_output, $output->getProcessedText());
     $this->assertEquals($cache_tag, $output->getCacheTags());
@@ -140,14 +140,14 @@ public function testEditorFileReferenceFilter() {
 
     // Image dimensions are present.
     $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" />';
-    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" ' . $dimensions . ' />';
+    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" ' . $dimensions . '>';
     $output = $test($input);
     $this->assertSame($expected_output, $output->getProcessedText());
     $this->assertEquals($cache_tag, $output->getCacheTags());
 
     // Image dimensions are set manually.
     $input = '<img src="llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '"width="41" height="21" />';
-    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" width="41" height="21" />';
+    $expected_output = '<img src="/' . $this->siteDirectory . '/files/llama.jpg" data-entity-type="file" data-entity-uuid="' . $uuid . '" width="41" height="21">';
     $output = $test($input);
     $this->assertSame($expected_output, $output->getProcessedText());
     $this->assertEquals($cache_tag, $output->getCacheTags());
diff --git a/core/modules/editor/tests/src/Unit/EditorXssFilter/StandardTest.php b/core/modules/editor/tests/src/Unit/EditorXssFilter/StandardTest.php
index 81c4e858d83ce492041a0aaa12d1f9f512390640..af2990ffbdad12dfd8bfd698cd5ac044483bc55e 100644
--- a/core/modules/editor/tests/src/Unit/EditorXssFilter/StandardTest.php
+++ b/core/modules/editor/tests/src/Unit/EditorXssFilter/StandardTest.php
@@ -517,12 +517,12 @@ public function providerTestFilterXss() {
     // @see \Drupal\editor\EditorXssFilter::filterXssDataAttributes()
 
     // The following two test cases verify that XSS attack vectors are filtered.
-    $data[] = ['<img src="butterfly.jpg" data-caption="&lt;script&gt;alert();&lt;/script&gt;" />', '<img src="butterfly.jpg" data-caption="alert();" />'];
-    $data[] = ['<img src="butterfly.jpg" data-caption="&lt;EMBED SRC=&quot;http://ha.ckers.org/xss.swf&quot; AllowScriptAccess=&quot;always&quot;&gt;&lt;/EMBED&gt;" />', '<img src="butterfly.jpg" data-caption="" />'];
+    $data[] = ['<img src="butterfly.jpg" data-caption="&lt;script&gt;alert();&lt;/script&gt;" />', '<img src="butterfly.jpg" data-caption="alert();">'];
+    $data[] = ['<img src="butterfly.jpg" data-caption="&lt;EMBED SRC=&quot;http://ha.ckers.org/xss.swf&quot; AllowScriptAccess=&quot;always&quot;&gt;&lt;/EMBED&gt;" />', '<img src="butterfly.jpg" data-caption>'];
 
     // When including HTML-tags as visible content, they are double-escaped.
     // This test case ensures that we leave that content unchanged.
-    $data[] = ['<img src="butterfly.jpg" data-caption="&amp;lt;script&amp;gt;alert();&amp;lt;/script&amp;gt;" />', '<img src="butterfly.jpg" data-caption="&amp;lt;script&amp;gt;alert();&amp;lt;/script&amp;gt;" />'];
+    $data[] = ['<img src="butterfly.jpg" data-caption="&amp;lt;script&amp;gt;alert();&amp;lt;/script&amp;gt;" />', '<img src="butterfly.jpg" data-caption="&amp;lt;script&amp;gt;alert();&amp;lt;/script&amp;gt;">'];
 
     return $data;
   }
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index d5a8182048a68b6b489a5dbdeb7b2f0db61ddf29..a5d0ee3b585f8461f2229fa03659884a4a26865d 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -386,6 +386,34 @@ function field_field_storage_config_update(FieldStorageConfigInterface $field_st
   }
 }
 
+/**
+ * Implements hook_ENTITY_TYPE_create() for 'field_config'.
+ *
+ * Determine the selection handler plugin ID for an entity reference field.
+ */
+function field_field_config_create(FieldConfigInterface $field) {
+  // Act on all sub-types of the entity_reference field type.
+  /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
+  $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
+  $item_class = 'Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem';
+  $class = $field_type_manager->getPluginClass($field->getType());
+  if ($class !== $item_class && !is_subclass_of($class, $item_class)) {
+    return;
+  }
+
+  // If we don't know the target type yet, there's nothing else we can do.
+  $target_type = $field->getFieldStorageDefinition()->getSetting('target_type');
+  if (empty($target_type)) {
+    return;
+  }
+
+  // Make sure the selection handler plugin is the correct derivative for the
+  // target entity type.
+  $selection_manager = \Drupal::service('plugin.manager.entity_reference_selection');
+  [$current_handler] = explode(':', $field->getSetting('handler'), 2);
+  $field->setSetting('handler', $selection_manager->getPluginId($target_type, $current_handler));
+}
+
 /**
  * Implements hook_ENTITY_TYPE_presave() for 'field_config'.
  *
@@ -396,6 +424,7 @@ function field_field_config_presave(FieldConfigInterface $field) {
   if ($field->isSyncing()) {
     return;
   }
+  field_field_config_create($field);
 
   // Act on all sub-types of the entity_reference field type.
   /** @var \Drupal\Core\Field\FieldTypePluginManager $field_type_manager */
@@ -406,13 +435,6 @@ function field_field_config_presave(FieldConfigInterface $field) {
     return;
   }
 
-  // Make sure the selection handler plugin is the correct derivative for the
-  // target entity type.
-  $target_type = $field->getFieldStorageDefinition()->getSetting('target_type');
-  $selection_manager = \Drupal::service('plugin.manager.entity_reference_selection');
-  [$current_handler] = explode(':', $field->getSetting('handler'), 2);
-  $field->setSetting('handler', $selection_manager->getPluginId($target_type, $current_handler));
-
   // In case we removed all the target bundles allowed by the field in
   // EntityReferenceItem::onDependencyRemoval() or field_entity_bundle_delete()
   // we have to log a critical message because the field will not function
diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php
index f43a7987487a6145ebe14141d657d14b55e3e23f..0c858c334a8b3c69503c6afad7955841648136b3 100644
--- a/core/modules/field/src/Entity/FieldConfig.php
+++ b/core/modules/field/src/Entity/FieldConfig.php
@@ -48,7 +48,8 @@
  *   constraints = {
  *     "RequiredConfigDependencies" = {
  *       "field_storage_config"
- *     }
+ *     },
+ *     "ImmutableProperties" = {"id", "entity_type", "field_name", "bundle", "field_type"},
  *   }
  * )
  */
diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php
index 0aa8dca37860c13cbe2522e7fd17feff2edc5de3..c385c416743724307925ee1646c01d98606e87fb 100644
--- a/core/modules/field/src/Entity/FieldStorageConfig.php
+++ b/core/modules/field/src/Entity/FieldStorageConfig.php
@@ -6,6 +6,8 @@
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\FieldableEntityStorageInterface;
+use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Field\FieldException;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\TypedData\OptionsProviderInterface;
@@ -46,6 +48,9 @@
  *     "indexes",
  *     "persist_with_no_fields",
  *     "custom_storage",
+ *   },
+ *   constraints = {
+ *     "ImmutableProperties" = {"id", "entity_type", "field_name", "type"},
  *   }
  * )
  */
@@ -279,6 +284,28 @@ public function id() {
     return $this->getTargetEntityTypeId() . '.' . $this->getName();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function postCreate(EntityStorageInterface $storage) {
+    parent::postCreate($storage);
+
+    // Check that the field type is known.
+    $field_type = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->type, FALSE);
+    if (!$field_type) {
+      throw new FieldException("Attempt to create a field storage of unknown type {$this->type}.");
+    }
+    $this->module = $field_type['provider'];
+
+    // Make sure all expected runtime settings are present.
+    $default_settings = \Drupal::service('plugin.manager.field.field_type')
+      ->getDefaultStorageSettings($this->getType());
+
+    // Filter out any unknown (unsupported) settings.
+    $supported_settings = array_intersect_key($this->getSettings(), $default_settings);
+    $this->set('settings', $supported_settings + $default_settings);
+  }
+
   /**
    * Overrides \Drupal\Core\Entity\Entity::preSave().
    *
@@ -319,7 +346,6 @@ public function preSave(EntityStorageInterface $storage) {
    */
   protected function preSaveNew(EntityStorageInterface $storage) {
     $entity_field_manager = \Drupal::service('entity_field.manager');
-    $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
 
     // Assign the ID.
     $this->id = $this->id();
@@ -337,13 +363,6 @@ protected function preSaveNew(EntityStorageInterface $storage) {
       throw new FieldException("Attempt to create field storage {$this->getName()} which is reserved by entity type {$this->getTargetEntityTypeId()}.");
     }
 
-    // Check that the field type is known.
-    $field_type = $field_type_manager->getDefinition($this->getType(), FALSE);
-    if (!$field_type) {
-      throw new FieldException("Attempt to create a field storage of unknown type {$this->getType()}.");
-    }
-    $this->module = $field_type['provider'];
-
     // Notify the field storage definition listener.
     \Drupal::service('field_storage_definition.listener')->onFieldStorageDefinitionCreate($this);
   }
@@ -666,7 +685,19 @@ public function getOptionsProvider($property_name, FieldableEntityInterface $ent
     // runtime item object, so that it can be used as the options provider
     // without modifying the entity being worked on.
     if (is_subclass_of($this->getFieldItemClass(), OptionsProviderInterface::class)) {
-      $items = $entity->get($this->getName());
+      try {
+        $items = $entity->get($this->getName());
+      }
+      catch (\InvalidArgumentException $e) {
+        // When a field doesn't exist, create a new field item list using a
+        // temporary base field definition. This step is necessary since there
+        // may not be a field configuration for the storage when creating a new
+        // field.
+        // @todo Simplify in https://www.drupal.org/project/drupal/issues/3347291.
+        $field_storage = BaseFieldDefinition::createFromFieldStorageDefinition($this);
+        $entity_adapter = EntityAdapter::createFromEntity($entity);
+        $items = \Drupal::typedDataManager()->create($field_storage, name: $field_storage->getName(), parent: $entity_adapter);
+      }
       return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0);
     }
     // @todo: Allow setting custom options provider, see
@@ -710,7 +741,7 @@ public function getTargetEntityTypeId() {
    *   TRUE if the field has data for any entity; FALSE otherwise.
    */
   public function hasData() {
-    return \Drupal::entityTypeManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
+    return !$this->isNew() && \Drupal::entityTypeManager()->getStorage($this->entity_type)->countFieldData($this, TRUE);
   }
 
   /**
diff --git a/core/modules/field/tests/modules/field_plugins_test/field_plugins_test.field_type_categories.yml b/core/modules/field/tests/modules/field_plugins_test/field_plugins_test.field_type_categories.yml
index 35b725c686308e4fbf6355d97ff301c308ddbcfd..fe3b94f826fe0d15284f06c255b6ac68ea9c04a6 100644
--- a/core/modules/field/tests/modules/field_plugins_test/field_plugins_test.field_type_categories.yml
+++ b/core/modules/field/tests/modules/field_plugins_test/field_plugins_test.field_type_categories.yml
@@ -2,3 +2,5 @@ test_category:
   label: 'Test category'
   description: 'This is a test field type category.'
   weight: -10
+  libraries:
+    - field_plugins_test/test_library
diff --git a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php
index 3be9c473a41bbd4fb0e0009e7f760cd30a212905..12362a73ffb4284932227b30f4ae5afdeef2e044 100644
--- a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php
+++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php
@@ -4,6 +4,7 @@
 
 use Behat\Mink\Element\NodeElement;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Messenger\MessengerInterface;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\node\Entity\Node;
 use Drupal\taxonomy\Entity\Vocabulary;
@@ -119,7 +120,7 @@ public function testFieldAdminHandler() {
     $edit = [
       'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
     ];
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Continue');
 
     // Add the view to the test field.
     $edit = [
@@ -131,6 +132,8 @@ public function testFieldAdminHandler() {
       'settings[handler_settings][view][view_and_display]' => 'node_test_view:entity_reference_1',
     ];
     $this->submitForm($edit, 'Save settings');
+    $this->assertSession()->statusMessageContains("Saved Test Entity Reference Field configuration.", MessengerInterface::TYPE_STATUS);
+    $this->assertFieldExistsOnOverview('Test Entity Reference Field');
 
     // Create nodes.
     $node1 = Node::create([
@@ -205,7 +208,7 @@ public function testFieldAdminHandler() {
     $edit = [
       'cardinality' => -1,
     ];
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->drupalGet($bundle_path . '/fields/' . $field_path);
     $term_name = $this->randomString();
     $result = \Drupal::entityQuery('taxonomy_term')
@@ -219,6 +222,7 @@ public function testFieldAdminHandler() {
       'settings[handler_settings][auto_create]' => 1,
     ];
     $this->submitForm($edit, 'Save settings');
+    $this->assertFieldExistsOnOverview($taxonomy_term_field_name);
     $this->drupalGet($bundle_path . '/fields/' . $field_path);
     $edit = [
       'set_default_value' => '1',
@@ -226,6 +230,7 @@ public function testFieldAdminHandler() {
       'default_value_input[field_' . $taxonomy_term_field_name . '][0][target_id]' => $term_name,
     ];
     $this->submitForm($edit, 'Save settings');
+    $this->assertFieldExistsOnOverview($taxonomy_term_field_name);
     // The term should now exist.
     $result = \Drupal::entityQuery('taxonomy_term')
       ->condition('name', $term_name)
@@ -382,7 +387,7 @@ protected function createEntityReferenceField($target_type, $bundles = []) {
       $field_edit['settings[handler_settings][target_bundles][' . $bundle . ']'] = TRUE;
     }
 
-    $this->fieldUIAddNewField($bundle_path, $field_name, NULL, 'entity_reference', $storage_edit, $field_edit);
+    $this->fieldUIAddNewField($bundle_path, $field_name, $field_name, 'entity_reference', $storage_edit, $field_edit);
 
     // Returns the generated field name.
     return $field_name;
diff --git a/core/modules/field/tests/src/Functional/FormTest.php b/core/modules/field/tests/src/Functional/FormTest.php
index 85e2d8a1b2cc1ac8dc504827ff0b8679a058de49..45eddc53cf0e00ac3d327a2fc2c290f38d5dae77 100644
--- a/core/modules/field/tests/src/Functional/FormTest.php
+++ b/core/modules/field/tests/src/Functional/FormTest.php
@@ -15,6 +15,7 @@
  * Tests field form handling.
  *
  * @group field
+ * @group #slow
  */
 class FormTest extends FieldTestBase {
 
@@ -578,9 +579,10 @@ public function testFieldFormAccess() {
     $this->assertEquals(2, $entity->{$field_name}->value, 'New revision has the expected value for the field with edit access.');
 
     // Check that the revision is also saved in the revisions table.
-    $entity = $this->container->get('entity_type.manager')
-      ->getStorage($entity_type)
-      ->loadRevision($entity->getRevisionId());
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = $this->container->get('entity_type.manager')
+      ->getStorage($entity_type);
+    $entity = $storage->loadRevision($entity->getRevisionId());
     $this->assertEquals(99, $entity->{$field_name_no_access}->value, 'New revision has the expected value for the field with no edit access.');
     $this->assertEquals(2, $entity->{$field_name}->value, 'New revision has the expected value for the field with edit access.');
   }
diff --git a/core/modules/field/tests/src/Functional/GenericTest.php b/core/modules/field/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2ab3881cc2d85d5c53e0f89751d92701c3029996
--- /dev/null
+++ b/core/modules/field/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\field\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for field.
+ *
+ * @group field
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
index f5317dcc85394ee918b519c0749eb9f1008549fb..9450f5f8c3cf01ae80981adab290fa85dbd179e9 100644
--- a/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
+++ b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php
@@ -11,6 +11,7 @@
  * Tests the creation of numeric fields.
  *
  * @group field
+ * @group #slow
  */
 class NumberFieldTest extends BrowserTestBase {
 
diff --git a/core/modules/field/tests/src/Functional/TranslationWebTest.php b/core/modules/field/tests/src/Functional/TranslationWebTest.php
index 54b1faf9d46a2e17eb178e5a16ed1e76f2c2ed94..30f01f4b8c1f1098effbcb69f75d747f968f3988 100644
--- a/core/modules/field/tests/src/Functional/TranslationWebTest.php
+++ b/core/modules/field/tests/src/Functional/TranslationWebTest.php
@@ -139,9 +139,10 @@ public function testFieldFormTranslationRevisions() {
    */
   private function checkTranslationRevisions($id, $revision_id, $available_langcodes) {
     $field_name = $this->fieldStorage->getName();
-    $entity = $this->container->get('entity_type.manager')
-      ->getStorage($this->entityTypeId)
-      ->loadRevision($revision_id);
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = $this->container->get('entity_type.manager')
+      ->getStorage($this->entityTypeId);
+    $entity = $storage->loadRevision($revision_id);
     foreach ($available_langcodes as $langcode => $value) {
       $passed = $entity->getTranslation($langcode)->{$field_name}->value == $value + 1;
       $this->assertTrue($passed, new FormattableMarkup('The @language translation for revision @revision was correctly stored', ['@language' => $langcode, '@revision' => $entity->getRevisionId()]));
diff --git a/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php
index 5e0fb206b50e569ecbdf7e80f89d8cfe6d76d6b3..84f20b3501be711bf33a69648982567d85712047 100644
--- a/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php
+++ b/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php
@@ -122,7 +122,7 @@ public function testFieldAdminHandler() {
     $this->drupalGet($bundle_path . '/fields/add-field');
 
     // Check if the commonly referenced entity types appear in the list.
-    $page->find('css', "[name='new_storage_type'][value='reference']")->click();
+    $page->find('css', "[name='new_storage_type'][value='reference']")->getParent()->click();
     $assert_session->waitForText('Choose an option below');
     $this->assertSession()->elementExists('css', "[name='group_field_options_wrapper'][value='field_ui:entity_reference:node']");
     $this->assertSession()->elementExists('css', "[name='group_field_options_wrapper'][value='field_ui:entity_reference:user']");
@@ -136,7 +136,7 @@ public function testFieldAdminHandler() {
     $this->assertFieldSelectOptions('settings[target_type]', array_keys(\Drupal::entityTypeManager()->getDefinitions()));
 
     // Second step: 'Field settings' form.
-    $this->submitForm([], 'Save field settings');
+    $this->submitForm([], 'Continue');
 
     // The base handler should be selected by default.
     $this->assertSession()->fieldValueEquals('settings[handler]', 'default:node');
@@ -268,7 +268,7 @@ public function testFieldAdminHandler() {
       'settings[target_type]' => 'taxonomy_term',
     ];
     $this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage');
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->drupalGet($bundle_path . '/fields/' . $field_name);
     $this->assertSession()->fieldExists('settings[handler_settings][auto_create]');
 
@@ -279,7 +279,7 @@ public function testFieldAdminHandler() {
       'settings[target_type]' => 'user',
     ];
     $this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage');
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->drupalGet($bundle_path . '/fields/' . $field_name);
     $this->assertSession()->fieldValueEquals('settings[handler_settings][filter][type]', '_none');
     $this->assertSession()->fieldValueEquals('settings[handler_settings][sort][field]', '_none');
@@ -299,7 +299,7 @@ public function testFieldAdminHandler() {
       'settings[target_type]' => 'node',
     ];
     $this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage');
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
 
     // Try to select the views handler.
     $this->drupalGet($bundle_path . '/fields/' . $field_name);
@@ -332,7 +332,7 @@ public function testFieldAdminHandler() {
       'settings[target_type]' => 'entity_test',
     ];
     $this->drupalGet($bundle_path . '/fields/' . $field_name . '/storage');
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->drupalGet($bundle_path . '/fields/' . $field_name);
     $page->findField('settings[handler]')->setValue('views');
     $assert_session
diff --git a/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php b/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php
index c4c4709e11914bb40fe177a259cdd1d92c2ffd5f..0d6b9bc0672315f62b51d70dedd68f90dddfbd33 100644
--- a/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php
+++ b/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php
@@ -86,4 +86,16 @@ public function testMultilineTextFieldDefaultValue(): void {
     $this->assertValidationErrors([]);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function testImmutableProperties(array $valid_values = []): void {
+    // If we don't clear the previous settings here, we will get unrelated
+    // validation errors (in addition to the one we're expecting), because the
+    // settings from the *old* field_type won't match the config schema for the
+    // settings of the *new* field_type.
+    $this->entity->set('settings', []);
+    parent::testImmutableProperties($valid_values);
+  }
+
 }
diff --git a/core/modules/field/tests/src/Kernel/Entity/FieldEntitySettingsTest.php b/core/modules/field/tests/src/Kernel/Entity/FieldEntitySettingsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..da442c604babd54b509aed43a01c210bdbec86e2
--- /dev/null
+++ b/core/modules/field/tests/src/Kernel/Entity/FieldEntitySettingsTest.php
@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\field\Kernel\Entity;
+
+use Drupal\entity_test\Entity\EntityTestBundle;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests the ways that field entities handle their settings.
+ *
+ * @group field
+ */
+class FieldEntitySettingsTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['entity_test', 'field'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    EntityTestBundle::create(['id' => 'test', 'label' => 'Test'])->save();
+  }
+
+  /**
+   * @group legacy
+   */
+  public function testFieldEntitiesCarryDefaultSettings(): void {
+    /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
+    $field_storage = FieldStorageConfig::create([
+      'type' => 'integer',
+      'entity_type' => 'entity_test',
+      'field_name' => 'test',
+    ]);
+    $field = FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => 'test',
+    ]);
+
+    /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $plugin_manager */
+    $plugin_manager = $this->container->get('plugin.manager.field.field_type');
+    $default_storage_settings = $plugin_manager->getDefaultStorageSettings('integer');
+    $default_field_settings = $plugin_manager->getDefaultFieldSettings('integer');
+
+    // Both entities should have the complete, default settings for their
+    // field type.
+    $this->assertSame($default_storage_settings, $field_storage->get('settings'));
+    $this->assertSame($default_field_settings, $field->get('settings'));
+
+    // If we try to set incomplete settings, the existing values should be
+    // retained.
+    $storage_settings = $field_storage->setSettings(['size' => 'big'])
+      ->get('settings');
+    // There should be no missing settings.
+    $missing_storage_settings = array_diff_key($default_storage_settings, $storage_settings);
+    $this->assertEmpty($missing_storage_settings);
+    // The value we set should be remembered.
+    $this->assertSame('big', $storage_settings['size']);
+
+    $field_settings = $field->setSetting('min', 10)->getSettings();
+    $missing_field_settings = array_diff_key($default_field_settings, $field_settings);
+    $this->assertEmpty($missing_field_settings);
+    $this->assertSame(10, $field_settings['min']);
+
+    $field_settings = $field->setSettings(['max' => 39])->get('settings');
+    $missing_field_settings = array_diff_key($default_field_settings, $field_settings);
+    $this->assertEmpty($missing_field_settings);
+    $this->assertSame(39, $field_settings['max']);
+
+    // Test that saving settings with incomplete settings is not triggering
+    // error, and values are retained.
+    $field_storage->save();
+    $missing_storage_settings = array_diff_key($default_storage_settings, $storage_settings);
+    $this->assertEmpty($missing_storage_settings);
+    // The value we set should be remembered.
+    $this->assertSame('big', $storage_settings['size']);
+
+    $field->save();
+    $missing_field_settings = array_diff_key($default_field_settings, $field_settings);
+    $this->assertEmpty($missing_field_settings);
+    $this->assertSame(39, $field_settings['max']);
+  }
+
+  /**
+   * Tests entity reference settings are normalized on field creation and save.
+   */
+  public function testEntityReferenceSettingsNormalized(): void {
+    $field_storage = FieldStorageConfig::create([
+      'field_name' => 'test_reference',
+      'type' => 'entity_reference',
+      'entity_type' => 'entity_test',
+      'cardinality' => 1,
+      'settings' => [
+        'target_type' => 'entity_test',
+      ],
+    ]);
+    $field_storage->save();
+
+    $field = FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => 'test',
+      'label' => 'Test Reference',
+      'settings' => [
+        'handler' => 'default',
+      ],
+    ]);
+    $this->assertSame('default:entity_test', $field->getSetting('handler'));
+    // If the handler is changed, it should be normalized again on pre-save.
+    $field->setSetting('handler', 'default')->save();
+    $this->assertSame('default:entity_test', $field->getSetting('handler'));
+  }
+
+}
diff --git a/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php b/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php
index 3ffbd0292048365d05da56c0b98f5b26c00f2d02..9e6fc34b7b4443538b2710997c993d9516bddb15 100644
--- a/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php
+++ b/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php
@@ -15,7 +15,7 @@ class FieldStorageConfigValidationTest extends ConfigEntityValidationTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['field', 'user'];
+  protected static $modules = ['field', 'node', 'user'];
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/field/tests/src/Kernel/FieldAttachStorageTest.php b/core/modules/field/tests/src/Kernel/FieldAttachStorageTest.php
index 751ec5cd5a0ebc2e2fd368a93e076697089e1c3c..844a33c173c8353376bbda97e5e459f2cf88ddc9 100644
--- a/core/modules/field/tests/src/Kernel/FieldAttachStorageTest.php
+++ b/core/modules/field/tests/src/Kernel/FieldAttachStorageTest.php
@@ -50,6 +50,7 @@ public function testFieldAttachSaveLoad() {
       $values[$current_revision] = $current_values;
     }
 
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->container->get('entity_type.manager')->getStorage($entity_type);
     $storage->resetCache();
     $entity = $storage->load($entity_id);
@@ -252,6 +253,7 @@ public function testFieldAttachDelete() {
     $entity->setNewRevision();
     $entity->save();
     $vids[] = $entity->getRevisionId();
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $controller */
     $controller = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
     $controller->resetCache();
 
diff --git a/core/modules/field/tests/src/Kernel/FieldDataCountTest.php b/core/modules/field/tests/src/Kernel/FieldDataCountTest.php
index f521d6236bab866f7f424245f388806abf95e271..2610b31c0a5536a09e1be359901fbc1be350e927 100644
--- a/core/modules/field/tests/src/Kernel/FieldDataCountTest.php
+++ b/core/modules/field/tests/src/Kernel/FieldDataCountTest.php
@@ -136,6 +136,7 @@ public function testEntityCountAndHasData() {
 
     $this->assertTrue($this->fieldTestData->field_storage_2->hasData(), 'There are entities with field data.');
 
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->container->get('entity_type.manager')->getStorage($entity_type);
     $entity = $storage->loadRevision($first_revision);
     $this->assertCount($cardinality, $entity->{$this->fieldTestData->field_name_2}, new FormattableMarkup('Revision %revision_id: expected number of values.', ['%revision_id' => $first_revision]));
diff --git a/core/modules/field/tests/src/Kernel/FieldTypeCategoryDiscoveryTest.php b/core/modules/field/tests/src/Kernel/FieldTypeCategoryDiscoveryTest.php
index 0dbf670fdcbeb26142def09cec5b48f4207ac0cb..22fa2ef738c2281e492b2b82e349ecbb126ba137 100644
--- a/core/modules/field/tests/src/Kernel/FieldTypeCategoryDiscoveryTest.php
+++ b/core/modules/field/tests/src/Kernel/FieldTypeCategoryDiscoveryTest.php
@@ -29,12 +29,14 @@ public function testFieldTypeCategories() {
       'Test category',
       'This is a test field type category.',
       -10,
+      ['field_plugins_test/test_library'],
     ];
 
     $this->assertSame($expected, [
       (string) $category->getLabel(),
       (string) $category->getDescription(),
       $category->getWeight(),
+      $category->getLibraries(),
     ]);
   }
 
diff --git a/core/modules/field/tests/src/Kernel/KernelString/StringFormatterTest.php b/core/modules/field/tests/src/Kernel/KernelString/StringFormatterTest.php
index 36551fb4acb20febc47b3f8f3466e8a085f6bb41..bfae3d99a5ab97a4b879739c5f24f461b4102373 100644
--- a/core/modules/field/tests/src/Kernel/KernelString/StringFormatterTest.php
+++ b/core/modules/field/tests/src/Kernel/KernelString/StringFormatterTest.php
@@ -163,7 +163,9 @@ public function testStringFormatter() {
     $value2 = $this->randomMachineName();
     $entity->{$this->fieldName}->value = $value2;
     $entity->save();
-    $entity_new_revision = $this->entityTypeManager->getStorage('entity_test_rev')->loadRevision($old_revision_id);
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage('entity_test_rev');
+    $entity_new_revision = $storage->loadRevision($old_revision_id);
 
     $this->renderEntityFields($entity, $this->display);
     $this->assertLink($value2, 0);
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php
index 8679f83f6dccb16f7724c8012604feffd0ec12b1..f2fd4711b154894326bbc82e7547cfa4d8d33cf2 100644
--- a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php
+++ b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFieldInstanceTest.php
@@ -150,7 +150,7 @@ public function testFieldInstances() {
     $this->assertLinkFields('node.blog.field_link', DRUPAL_REQUIRED);
 
     $this->assertEntityReferenceFields('node.article.field_tags', ['tags']);
-    $this->assertEntityReferenceFields('node.forum.taxonomy_forums', ['forums']);
+    $this->assertEntityReferenceFields('node.forum.taxonomy_forums', ['sujet_de_discussion']);
     $this->assertEntityReferenceFields('node.test_content_type.field_term_reference', ['tags', 'test_vocabulary']);
 
     // Tests that fields created by the Title module are not migrated.
diff --git a/core/modules/field_layout/tests/src/Functional/GenericTest.php b/core/modules/field_layout/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e1abd414655076c9c9d9c1b0c5cabf5de9dfadc
--- /dev/null
+++ b/core/modules/field_layout/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\field_layout\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for field_layout.
+ *
+ * @group field_layout
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/field_ui/css/field_ui.icons.theme.css b/core/modules/field_ui/css/field_ui.icons.theme.css
index d45a5158d15cfc0a2e09e51a620c5590dc52dedd..6679d13efa41736624dc3b154835c93bb9d594aa 100644
--- a/core/modules/field_ui/css/field_ui.icons.theme.css
+++ b/core/modules/field_ui/css/field_ui.icons.theme.css
@@ -8,30 +8,29 @@
 .field-option__icon {
   position: relative;
   height: 100%;
-  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='22' height='21' fill='none'%3e%3cpath stroke='%23CACBD2' stroke-width='2' d='M1 4.012h6c-.5-3.5 5-4.5 5 0h5v5c4-.5 6 6 0 6v4.5H1v-16.5'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg fill='none' height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m5 7.71798 8.7937-.00001c-.7328-5.48304 7.3282-7.049628 7.3282 0l7.3281.00001v7.83292c5.8625-.7833 8.7937 9.3995 0 9.3995v7.0496h-23.45v-11.7494-14.02745' stroke='%23cacbd2' stroke-width='3'/%3e%3c/svg%3e");
   background-repeat: no-repeat;
   background-position: center;
-  background-size: 60%;
+  background-size: 36px;
 }
 .field-icon-boolean {
-  background-image: url("data:image/svg+xml,%3csvg width='24' height='16' viewBox='0 0 24 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3crect x='1' y='1' width='22' height='14' rx='7' stroke='%2355565B' stroke-width='2'/%3e%3crect x='12' y='4' width='8' height='8' rx='4' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m11.02 6.064c-2.287.164-4.788 1.165-6.58 2.634-1.741 1.427-3.084 3.366-3.786 5.466-.852 2.547-.853 5.12-.006 7.656 1.506 4.503 5.535 7.679 10.302 8.119.884.082 13.216.082 14.1 0 5.287-.488 9.574-4.301 10.683-9.502.649-3.043.026-6.328-1.707-8.989a11.927 11.927 0 0 0 -9.157-5.386c-.977-.071-12.861-.069-13.849.002m14.422 2.542c4.167.683 7.319 3.848 7.953 7.984.165 1.079.088 2.688-.182 3.75-.944 3.727-4.045 6.501-7.923 7.088-.789.12-13.787.12-14.58.001-3.514-.53-6.376-2.828-7.627-6.126-.631-1.664-.746-3.857-.295-5.645.918-3.647 3.936-6.404 7.712-7.047.692-.118 14.227-.122 14.942-.005m-2.702 2.548c-2.256.498-3.999 2.206-4.569 4.476-.156.618-.219 2.389-.115 3.18.4 3.027 2.917 5.25 5.944 5.25a5.87 5.87 0 0 0 4.37-1.894 6.1 6.1 0 0 0 1.576-3.415c.1-.847.038-2.503-.117-3.121-.446-1.782-1.586-3.196-3.219-3.994-.879-.43-1.377-.546-2.46-.573-.72-.017-1.002.001-1.41.091' fill='%2355565b'/%3e%3c/svg%3e");
 }
 .field-icon-plain_text {
-  background-image: url("data:image/svg+xml,%3csvg width='12' height='15' viewBox='0 0 12 15' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M0 2V0H12V2H7V15H5V2H0Z' fill='%2355565B'/%3e%3c/svg%3e");
-  background-size: 38%;
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36' viewBox='0 0 36 36'%3e  %3cpath fill='%2355565B' d='M7 7.60001V4H29V7.60001H19.8333V31H16.1667V7.60001H7Z'/%3e%3c/svg%3e");
 }
 .field-icon-date_time {
-  background-image: url("data:image/svg+xml,%3csvg width='23' height='21' fill='none' xmlns='http://www.w3.org/2000/svg'%3e  %3cpath d='M19 14.857h-2V12h-1v4h3v-1.143Z' fill='%2355565B'/%3e  %3cpath d='M17.125 1.75h-4.004V0h-1.75v1.75H6.123V0h-1.75v1.75H.876A.875.875 0 0 0 0 2.623v14.501c0 .483.392.875.875.875h11.248a5.603 5.603 0 0 1-.954-2H2V6.123h14v2.911a5.69 5.69 0 0 1 2 .135V2.624a.875.875 0 0 0-.875-.875Z' fill='%2355565B'/%3e  %3cpath fill-rule='evenodd' clip-rule='evenodd' d='M16.626 18.503a3.877 3.877 0 1 0 0-7.753 3.877 3.877 0 0 0 0 7.753Zm0 1.75a5.627 5.627 0 1 0 0-11.253 5.627 5.627 0 0 0 0 11.253Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='33' viewBox='0 0 36 33' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m7.08 1.44v1.44h-5.976l-.318.158a1.326 1.326 0 0 0 -.726.941c-.048.224-.061 3.678-.048 12.311l.018 12 .131.246c.073.135.228.329.345.432.448.393-.104.373 9.978.372l9.14-.002.288.346c.479.574 1.348 1.362 1.936 1.755a9.006 9.006 0 0 0 8.182.98c4.629-1.704 7.072-6.881 5.452-11.555-.939-2.711-3.044-4.81-5.725-5.709l-.627-.211-.03-5.537-.03-5.537-.133-.249c-.162-.303-.513-.603-.797-.682-.125-.035-1.57-.058-3.555-.059h-3.345v-2.88h-2.82v2.88h-8.52v-2.88h-2.82zm18.84 10.912v2.391l-.342.041c-.542.063-1.317.269-1.969.521-2.825 1.095-4.943 3.609-5.613 6.664-.235 1.07-.219 2.683.039 3.936l.04.195h-14.835v-16.14h22.68zm1.185 2.332a2.601 2.601 0 0 1 -.45 0c-.124-.013-.022-.024.225-.024s.349.011.225.024m1.332 3.012c.586.148 1.445.539 1.976.899a6.322 6.322 0 0 1 2.746 5.525c-.079 1.624-.71 3.058-1.845 4.194a5.756 5.756 0 0 1 -1.756 1.24c-.918.435-1.576.581-2.618.583-.585.001-1.008-.03-1.292-.094-2.621-.594-4.532-2.609-4.95-5.219-.107-.664-.045-1.976.121-2.594.636-2.361 2.568-4.177 4.912-4.62.665-.125 2.042-.081 2.706.086m-2.563 5.119.016 3.255 2.415.016 2.415.015v-1.859l-1.605-.016-1.605-.016-.016-2.325-.015-2.325h-1.62z' fill='%2355565b'/%3e%3c/svg%3e");
 }
 .field-icon-email {
-  background-image: url("data:image/svg+xml,%3csvg width='17' height='17' fill='none' xmlns='http://www.w3.org/2000/svg'%3e  %3cpath d='M8.954.748C6.498.738 3.977 1.815 2.538 3.84 1.041 5.917.663 8.658 1.158 11.135c.35 1.653 1.316 3.216 2.796 4.08 1.688 1.02 3.747 1.173 5.671.953a9.483 9.483 0 0 0 2.8-.78v-1.83c-1.985.823-4.254 1.224-6.364.633-1.482-.418-2.687-1.696-3.013-3.2-.39-1.533-.295-3.18.198-4.675.528-1.494 1.674-2.787 3.178-3.33a7.097 7.097 0 0 1 4.756-.134c1.364.5 2.477 1.679 2.817 3.091.354 1.293.314 2.721-.186 3.965-.22.525-.72 1.06-1.34.94a.783.783 0 0 1-.647-.692c-.183-.867.042-1.753.028-2.63.047-.886.094-1.772.143-2.659-1.4-.419-2.911-.688-4.355-.35-1.462.4-2.583 1.716-2.827 3.206-.214 1.11-.121 2.308.392 3.317.474.865 1.393 1.48 2.389 1.519 1.098.103 2.269-.356 2.893-1.284.407.928 1.495 1.456 2.483 1.268 1.18-.162 2.131-1.085 2.547-2.167.687-1.605.656-3.453.18-5.112-.568-1.831-1.982-3.398-3.806-4.035A7.905 7.905 0 0 0 8.954.748ZM8.838 6.07c.389-.001.778.03 1.155.112-.11 1.265.025 2.59-.461 3.788-.233.506-.741.899-1.308.891-.594.059-1.253-.29-1.392-.902-.265-.812-.187-1.711.056-2.517.271-.74.963-1.355 1.779-1.363a2.63 2.63 0 0 1 .171-.01Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m18.905 3.0034c-4.8818-.0181-9.89368 2.11996-12.75421 6.14588-2.97685 4.12852-3.72724 9.57882-2.74182 14.50242.69215 3.2862 2.61415 6.3919 5.55652 8.109 3.35621 2.0297 7.44861 2.3335 11.27361 1.8957 1.9198-.2327 3.8097-.7456 5.5656-1.549 0-1.2133 0-2.4267 0-3.64-3.9461 1.6362-8.4574 2.4333-12.6514 1.2587-2.9465-.8305-5.34152-3.3703-5.98961-6.3624-.77366-3.0458-.58571-6.3211.39477-9.2927 1.05002-2.9697 3.32674-5.53882 6.31624-6.61934 2.9829-1.16767 6.4097-1.27462 9.4541-.26651 2.7133.99524 4.9245 3.33755 5.6015 6.14525.7038 2.5698.6228 5.4088-.3714 7.8826-.4383 1.0424-1.4289 2.1055-2.6643 1.867-.6836-.1102-1.2174-.6898-1.2841-1.374-.3646-1.7236.0832-3.4856.0543-5.2278.0939-1.7622.1876-3.5244.2846-5.2865-2.7816-.8329-5.7863-1.36856-8.6563-.6962-2.9057.7966-5.1346 3.4114-5.6209 6.3736-.4246 2.2055-.2402 4.5877.7799 6.5936.9431 1.7193 2.7689 2.9433 4.7485 3.0192 2.1842.205 4.5109-.7068 5.752-2.5513.808 1.8442 2.9703 2.8932 4.9355 2.5197 2.3445-.3217 4.2363-2.1564 5.0624-4.3086 1.3658-3.1906 1.3042-6.8642.3573-10.1616-1.129-3.63941-3.9388-6.75356-7.5656-8.02092-1.8577-.69892-3.8521-.9948-5.8372-.95578zm-.2305 10.5789c.7719-.0025 1.547.0602 2.296.2236-.2194 2.5144.0496 5.147-.9169 7.5287-.4626 1.006-1.4737 1.788-2.6009 1.773-1.18.1157-2.4907-.5777-2.7663-1.7944-.5272-1.6144-.3716-3.4013.1106-5.0038.5405-1.4722 1.9158-2.6924 3.5363-2.7087.1134-.0098.2273-.016.3412-.0184z' fill='%2355565b'/%3e%3c/svg%3e");
 }
 .field-icon-number {
-  background-image: url("data:image/svg+xml,%3csvg width='24' height='14' fill='none' xmlns='http://www.w3.org/2000/svg'%3e  %3cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.654 4.697H6.848v-.292C6.848 2.303 8.6.6 10.76.6c2.16 0 3.913 1.703 3.913 3.805 0 .907-.327 1.74-.872 2.394l-.003.004-3.992 4.625h4.867v1.757H6.848v-1.06l5.527-6.404c.307-.356.493-.815.493-1.316 0-1.132-.944-2.049-2.107-2.049-1.164 0-2.107.917-2.107 2.049v.292ZM23.216.976v1.2l-2.703 3.332C22.23 5.962 23.5 7.572 23.5 9.49c0 2.27-1.78 4.11-3.975 4.11-1.863 0-3.426-1.325-3.857-3.113l-.068-.284 1.652-.428.07.285c.245 1.022 1.14 1.778 2.203 1.778 1.254 0 2.271-1.051 2.271-2.348S20.78 7.14 19.525 7.14a2.2 2.2 0 0 0-1.008.244l-.37.204-.626-1.135 3.016-3.717h-4.419V.976h7.098Z' fill='%2355565B'/%3e  %3cpath d='M4.891.976v12.209H2.935V2.88L0 3.566V1.802L3.544.976h1.347Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m15.497 8.045c-1.353.129-2.556.713-3.559 1.727-1.08 1.092-1.675 2.516-1.677 4.013l-.001.555h2.683l.032-.585c.064-1.137.567-1.998 1.515-2.591.955-.598 2.344-.579 3.304.046 1.046.68 1.594 1.871 1.419 3.085-.134.93-.07.844-4.719 6.377l-4.231 5.038-.002.825-.001.825h11.7v-2.699l-3.627-.016-3.628-.015 2.974-3.54c1.635-1.947 3.08-3.689 3.212-3.87.322-.446.668-1.176.866-1.83.148-.487.164-.634.168-1.5.003-.82-.016-1.035-.133-1.47-.174-.647-.634-1.595-1.02-2.104-1.223-1.611-3.215-2.469-5.275-2.271m-12.872 1.184-2.625.635v1.338c0 .736.01 1.338.023 1.338.012 0 .91-.213 1.995-.473 1.085-.261 2.06-.492 2.167-.515l.195-.042v15.85h2.94v-18.78l-1.035.007-1.035.007zm21.495.701v1.35h3.3c1.815 0 3.3.013 3.3.028 0 .023-4.162 5.318-4.411 5.612-.064.075-.004.224.366.9.243.445.45.832.46.859.01.028.233-.06.496-.195 1.06-.541 1.997-.569 3.012-.087.814.387 1.449 1.12 1.781 2.06.161.457.181.589.181 1.203.001.492-.03.793-.108 1.05-.534 1.778-2.246 2.891-3.886 2.527-1.343-.299-2.334-1.279-2.686-2.655-.082-.322-.129-.41-.211-.403-.058.005-.602.14-1.209.3-.864.228-1.105.312-1.105.389 0 .214.317 1.188.538 1.654.833 1.753 2.35 2.971 4.166 3.345.74.153 1.734.13 2.465-.055a5.578 5.578 0 0 0 2.596-1.435c3.055-2.897 2.51-8.072-1.077-10.218a6 6 0 0 0 -.9-.424c-.257-.091-.467-.179-.467-.195s.905-1.175 2.01-2.574l2.009-2.544v-1.842h-10.62z' fill='%2355565b'/%3e%3c/svg%3e");
 }
 .field-icon-reference {
-  background-image: url("data:image/svg+xml,%3csvg width='18' height='18' fill='none' xmlns='http://www.w3.org/2000/svg'%3e  %3cpath d='M12 0a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-2v2h4a1 1 0 0 1 1 1v3h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2v-2H5v2h2a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h2V9a1 1 0 0 1 1-1h4V6H6a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h6ZM6 14H2v2h4v-2Zm10 0h-4v2h4v-2ZM11 2H7v2h4V2Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m10.98 9v4.98h5.52v3.06h-9v6h-5.52v9.96h14.04v-9.96h-5.52v-3.06h15v3.06h-5.52v9.96h14.04v-9.96h-5.52v-6h-9v-3.06h5.52v-9.96h-14.04zm11.026.015-.016 1.995h-7.98l-.016-1.995-.016-1.995h8.044zm-8.986 18.975v2.01h-8.04v-4.02h8.04zm18 0v2.01h-8.04v-4.02h8.04z' fill='%2355565b'/%3e%3c/svg%3e");
 }
 .field-icon-daterange {
-  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='none'%3e  %3cpath d='M15 2h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h4V0h2v2h6V0h2v2ZM2 8v10h16V8H2Zm2 2h2v2H4v-2Zm5 0h2v2H9v-2Zm5 0h2v2h-2v-2Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m27 3.6h7.2c.9941 0 1.8.8059 1.8 1.8v28.8c0 .9941-.8059 1.8-1.8 1.8h-32.4c-.994104 0-1.8-.8059-1.8-1.8v-28.8c0-.9941.805896-1.8 1.8-1.8h7.2v-3.6h3.6v3.6h10.8v-3.6h3.6zm-23.4 10.8v18h28.8v-18zm3.6 3.6h3.6v3.6h-3.6zm9 0h3.6v3.6h-3.6zm9 0h3.6v3.6h-3.6z' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/field_ui/css/field_ui.icons.theme.pcss.css b/core/modules/field_ui/css/field_ui.icons.theme.pcss.css
index a0acd96ec315df5c4087a57acf3c8bc3ec541bfd..09bf9cd6a57f9b77f09db962ea4ea08db25b673a 100644
--- a/core/modules/field_ui/css/field_ui.icons.theme.pcss.css
+++ b/core/modules/field_ui/css/field_ui.icons.theme.pcss.css
@@ -5,7 +5,7 @@
   background-image: url(../../../misc/icons/cacbd2/puzzle_piece_placeholder.svg);
   background-repeat: no-repeat;
   background-position: center;
-  background-size: 60%;
+  background-size: 36px;
 }
 
 .field-icon-boolean {
@@ -14,7 +14,6 @@
 
 .field-icon-plain_text {
   background-image: url(../../../misc/icons/55565b/plain_text.svg);
-  background-size: 38%;
 }
 
 .field-icon-date_time {
diff --git a/core/modules/field_ui/css/field_ui_add_field.module.css b/core/modules/field_ui/css/field_ui_add_field.module.css
index 4d6f2df5349413a17430c938af85c99d8da4d079..3e514351337d9c0344a65b62a8937e4a1c381924 100644
--- a/core/modules/field_ui/css/field_ui_add_field.module.css
+++ b/core/modules/field_ui/css/field_ui_add_field.module.css
@@ -1,54 +1,87 @@
+/*
+ * DO NOT EDIT THIS FILE.
+ * See the following change record for more information,
+ * https://www.drupal.org/node/3084859
+ * @preserve
+ */
+
 /**
  * @file field_ui_add_field.module.css
  */
 
 .field-ui-field-storage-add-form {
-  --option-padding: 0.625rem;
-  --suboption-padding: 1rem;
-
-  max-width: 80rem;
+  --thumb-size: 4.5rem;
 }
 
 .field-ui-new-storage-wrapper {
   margin-bottom: 0.75rem;
 }
 
+.group-field-options-wrapper {
+  margin-block: 1.5em;
+}
+
+.add-field-container,
+.group-field-options {
+  display: grid;
+  gap: 0.75rem 1.25rem;
+  margin-block: 0.625rem;
+}
+
+@media (min-width: 45rem) {
+  .add-field-container,
+  .group-field-options {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+@media (min-width: 75rem) {
+  .add-field-container {
+    grid-template-columns: repeat(3, 1fr);
+  }
+}
+
+@media (min-width: 87.5rem) {
+  .add-field-container {
+    grid-template-columns: repeat(4, 1fr);
+  }
+}
+
+.field-option {
+  display: grid;
+  grid-template-columns: var(--thumb-size) auto;
+  align-items: center;
+  padding: 0.25rem;
+  padding-inline-end: 0.75rem;
+  border: 1px solid var(--color-gray-100, #dedfe4);
+  gap: 0.75rem;
+  border-radius: 0.25rem;
+}
+
 .field-option__item {
   display: grid;
   grid-template-rows: auto 2fr;
-  grid-template-columns: auto 2rem;
+  grid-template-columns: auto 1.1875rem;
+  align-items: center;
   width: 100%;
   margin: 0;
+  column-gap: 1.25rem;
+  padding-block: 0.25rem;
 }
-.field-option__item label,
-.field-option__item div {
+
+.field-option__item > *:not(input) {
   grid-column: 1;
 }
-.field-option__item input {
-  grid-row: 1 / span 2;
+
+.field-option__item > input {
+  grid-row: 1 / -1;
   grid-column: 2;
-  margin-block: auto;
-  margin-left: 7px;
 }
 
 .field-option__thumb {
-  width: 4.5rem;
-  min-height: 2em;
-  margin-block: calc(var(--option-padding) / -2);
-  margin-inline: calc(var(--option-padding) / -2) 0.75rem;
-  background: var(--color-gray-050, #f3f4f9);
-}
-
-.group-field-options-wrapper {
-  margin-block: 1.5em;
-}
-
-.field-option {
-  display: flex;
-  margin-block: 10px;
-  padding: var(--option-padding);
-  border: 1px solid var(--color-gray-100, #dedfe4);
-  border-radius: 4px;
+  height: 100%;
+  min-height: var(--thumb-size);
+  background-color: var(--color-gray-050, #f3f4f9);
 }
 
 .subfield-option {
@@ -56,76 +89,53 @@
   padding: 1rem;
   padding-inline-end: 2rem;
   border: 1px solid var(--color-gray-200, #d3d4d9);
-  border-radius: 4px;
-}
-.subfield-option .item-list ul {
-  margin-inline: 0;
+  border-radius: 0.25rem;
 }
 
-.field-option:has(.field-option-radio:checked),
-.subfield-option:has(.field-option-radio:checked) {
-  border: 3px solid var(--color-blue-600, #003ecc);
+.subfield-option .field-option-radio {
+  margin-inline-end: 0.4375rem;
 }
 
-.field-option:has(.field-option-radio:checked) {
-  padding: calc(var(--option-padding) - 2px);
-}
-.subfield-option:has(.field-option-radio:checked) {
-  padding: calc(1rem - 2px);
-  padding-inline-end: calc(2rem - 2px);
+.subfield-option .item-list ul {
+  margin-inline: 0;
 }
 
-.field-option:hover,
-.subfield-option:hover {
-  padding: calc(var(--option-padding) - 1px);
-  border: 2px solid #232429;
+.field-option,
+.subfield-option {
+  cursor: pointer;
 }
 
-.subfield-option:hover {
-  padding: calc(1rem - 1px);
-  padding-inline-end: calc(2rem - 1px);
+.field-option.focus,
+.subfield-option.focus {
+  outline: 3px solid var(--color-focus);
+  outline-offset: 2px;
 }
 
-.field-option:has(.field-option-radio.error),
-.subfield-option:has(.field-option-radio.error) {
-  border: 2px solid #dc2323;
+.field-option.hover,
+.subfield-option.hover {
+  border-color: var(--color-gray);
+  box-shadow: inset 0 0 0 1px var(--color-gray), var(--details-box-shadow);
 }
 
-.field-option:has(.field-option-radio.error) {
-  padding: calc(var(--option-padding) - 1px);
-}
-.subfield-option:has(.field-option-radio.error) {
-  padding: calc(var(--suboption-padding) - 1px);
+.field-option:not(.selected, .error):hover .form-boolean,
+.subfield-option:not(.selected, .error):hover .form-boolean {
+  border-color: var(--input-fg-color);
+  box-shadow: inset 0 0 0 1px var(--input-fg-color);
 }
 
-
-/**
- * Add default focus styles to container.
- */
-.field-option:has(.field-option-radio:focus),
-.subfield-option:has(.field-option-radio:focus) {
-  outline: var(--focus-outline, -webkit-focus-ring-color) !important;
-  box-shadow: var(--focus-box-shadow) !important;
+.field-option.selected,
+.subfield-option.selected {
+  border-color: var(--color-blue);
+  box-shadow: inset 0 0 0 2px var(--color-blue-600), var(--details-box-shadow);
 }
 
-@media (min-width: 45rem) {
-  .add-field-container,
-  .group-field-options {
-    display: grid;
-    grid-template-columns: repeat(2, auto);
-  }
-
-  .subfield-option.subfield-option.subfield-option,
-  .field-option {
-    margin-inline-end: 1.25rem;
-  }
-}
-@media (min-width: 75rem) {
-  .add-field-container {
-    grid-template-columns: repeat(3, auto);
-  }
+.field-option.error,
+.subfield-option.error {
+  border-color: var(--color-red-500);
+  box-shadow: inset 0 0 0 1px var(--color-red-500), var(--details-box-shadow);
 }
 
-.subfield-option > .field-option-radio {
-  margin-right: 5px;
+.field-option .form-item__label.has-error,
+.subfield-option .form-item__label.has-error {
+  color: currentColor;
 }
diff --git a/core/modules/field_ui/css/field_ui_add_field.module.pcss.css b/core/modules/field_ui/css/field_ui_add_field.module.pcss.css
new file mode 100644
index 0000000000000000000000000000000000000000..9a7438dc7c195ff4f277915e17f92294aa8d580f
--- /dev/null
+++ b/core/modules/field_ui/css/field_ui_add_field.module.pcss.css
@@ -0,0 +1,117 @@
+/**
+ * @file field_ui_add_field.module.css
+ */
+
+.field-ui-field-storage-add-form {
+  --thumb-size: 72px;
+}
+
+.field-ui-new-storage-wrapper {
+  margin-bottom: 0.75rem;
+}
+
+.group-field-options-wrapper {
+  margin-block: 1.5em;
+}
+
+.add-field-container,
+.group-field-options {
+  display: grid;
+  gap: 0.75rem 1.25rem;
+  margin-block: 0.625rem;
+
+  @media (min-width: 45rem) {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
+
+.add-field-container {
+  @media (min-width: 75rem) {
+    grid-template-columns: repeat(3, 1fr);
+  }
+
+  @media (min-width: 87.5rem) {
+    grid-template-columns: repeat(4, 1fr);
+  }
+}
+
+.field-option {
+  display: grid;
+  grid-template-columns: var(--thumb-size) auto;
+  align-items: center;
+  padding: 0.25rem;
+  padding-inline-end: 0.75rem;
+  border: 1px solid var(--color-gray-100, #dedfe4);
+  gap: 0.75rem;
+  border-radius: 4px;
+}
+
+.field-option__item {
+  display: grid;
+  grid-template-rows: auto 2fr;
+  grid-template-columns: auto 1.1875rem;
+  align-items: center;
+  width: 100%;
+  margin: 0;
+  column-gap: 1.25rem;
+  padding-block: 0.25rem;
+
+  > *:not(input) {
+    grid-column: 1;
+  }
+
+  > input {
+    grid-row: 1 / -1;
+    grid-column: 2;
+  }
+}
+
+.field-option__thumb {
+  height: 100%;
+  min-height: var(--thumb-size);
+  background-color: var(--color-gray-050, #f3f4f9);
+}
+
+.subfield-option {
+  margin-block: 0.625rem;
+  padding: 1rem;
+  padding-inline-end: 2rem;
+  border: 1px solid var(--color-gray-200, #d3d4d9);
+  border-radius: 4px;
+
+  .field-option-radio {
+    margin-inline-end: 0.4375rem;
+  }
+
+  .item-list ul {
+    margin-inline: 0;
+  }
+}
+
+.field-option,
+.subfield-option {
+  cursor: pointer;
+  &.focus {
+    outline: 3px solid var(--color-focus);
+    outline-offset: 2px;
+  }
+  &.hover {
+    border-color: var(--color-gray);
+    box-shadow: inset 0 0 0 1px var(--color-gray), var(--details-box-shadow);
+  }
+  &:not(.selected, .error):hover .form-boolean {
+    border-color: var(--input-fg-color);
+    box-shadow: inset 0 0 0 1px var(--input-fg-color);
+  }
+  &.selected {
+    border-color: var(--color-blue);
+    box-shadow: inset 0 0 0 2px var(--color-blue-600), var(--details-box-shadow);
+  }
+  &.error {
+    border-color: var(--color-red-500);
+    box-shadow: inset 0 0 0 1px var(--color-red-500), var(--details-box-shadow);
+  }
+  & .form-item__label.has-error {
+    color: currentColor;
+  }
+}
diff --git a/core/modules/field_ui/css/field_ui_add_field.theme.css b/core/modules/field_ui/css/field_ui_add_field.theme.css
index e10346fa01a48c9ecf99f6447a2136e82770b7c4..aa1f56f66c642420bc54a4662aad465a5da409c2 100644
--- a/core/modules/field_ui/css/field_ui_add_field.theme.css
+++ b/core/modules/field_ui/css/field_ui_add_field.theme.css
@@ -1,20 +1,12 @@
 /**
  * @file field_ui_add_field.theme.css
  */
-:root {
-  --shadow-z1: 0 2px 4px rgba(0, 0, 0, 0.1);
-  --shadow-z2: 0 4px 10px rgba(0, 0, 0, 0.1);
-}
-
-.add-field-container .field-option:not(:focus),
-.group-field-options .subfield-option:not(:focus) {
-  box-shadow: var(--shadow-z2);
-}
-
 .field-option .form-item__label,
 .subfield-option.subfield-option .form-item__label {
+  margin: 0;
   font-size: 16px;
-  font-weight: revert;
+  font-weight: 400;
+  line-height: 1.5;
 }
 
 .subfield-option.subfield-option .form-item__label {
@@ -23,5 +15,7 @@
 
 .field-option .field-option__description,
 .subfield-option .description .item-list {
+  color: var(--color-gray-700);
   font-size: 14px;
+  line-height: 1.2;
 }
diff --git a/core/modules/field_ui/field_ui.libraries.yml b/core/modules/field_ui/field_ui.libraries.yml
index ffecdb2f9e2020521d1bd1eb66b3007ae37c8ea6..8aa2a27ac4fd7273ab9e89f54055439638ed68a8 100644
--- a/core/modules/field_ui/field_ui.libraries.yml
+++ b/core/modules/field_ui/field_ui.libraries.yml
@@ -1,7 +1,7 @@
 drupal.field_ui:
   version: VERSION
   js:
-    field_ui.js: {}
+    js/field_ui.js: {}
   css:
     theme:
       css/field_ui.admin.css: {}
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 7792ee8542144ed3d3bf57c92f43ffe5ad7c4d55..46c983503a5d0db7e0dda851487ecbee216f566e 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityFormModeInterface;
 use Drupal\Core\Url;
 use Drupal\field_ui\FieldUI;
+use Drupal\field_ui\Form\FieldConfigEditForm;
 use Drupal\field_ui\Plugin\Derivative\FieldUiLocalTask;
 
 /**
@@ -79,6 +80,7 @@ function field_ui_theme() {
 function field_ui_entity_type_build(array &$entity_types) {
   /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
   $entity_types['field_config']->setFormClass('edit', 'Drupal\field_ui\Form\FieldConfigEditForm');
+  $entity_types['field_config']->setFormClass('default', FieldConfigEditForm::class);
   $entity_types['field_config']->setFormClass('delete', 'Drupal\field_ui\Form\FieldConfigDeleteForm');
   $entity_types['field_config']->setListBuilderClass('Drupal\field_ui\FieldConfigListBuilder');
 
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/js/field_ui.js
similarity index 90%
rename from core/modules/field_ui/field_ui.js
rename to core/modules/field_ui/js/field_ui.js
index 6364dbd845134b8de31a039321fe8bda3739485d..bc7d6efe2af653e8a079265e7ab3d0d23996480f 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/js/field_ui.js
@@ -510,7 +510,8 @@
   };
 
   /**
-   * Allows users to select an element which checks a radio button.
+   * Allows users to select an element which checks a radio button and
+   * adds a class used for css styling on different states.
    *
    * @type {Drupal~behavior}
    *
@@ -519,24 +520,49 @@
    */
   Drupal.behaviors.clickToSelect = {
     attach(context) {
-      $(once('field-click-to-select', '.js-click-to-select', context)).on(
-        'click',
-        (event) => {
-          const clickToSelect = event.target.closest('.js-click-to-select');
-          const input = clickToSelect.querySelector('input');
-          input.checked = true;
-          // Ensure focus is added at the end of the process so wrap in
-          // a timeout.
-          setTimeout(() => {
-            // Remove the disabled attribute added by Drupal ajax so the
-            // element is focusable. This is safe as clicking the element
-            // multiple times causes no problems.
-            input.removeAttribute('disabled');
-            input.focus();
-          }, 0);
-          $(input).trigger('updateOptions');
+      once('field-click-to-select', '.js-click-to-select', context).forEach(
+        (clickToSelectEl) => {
+          const input = clickToSelectEl.querySelector('input');
+          if (input) {
+            Drupal.behaviors.clickToSelect.clickHandler(clickToSelectEl, input);
+          }
+          if (input.classList.contains('error')) {
+            clickToSelectEl.classList.add('error');
+          }
+          if (input.checked) {
+            this.selectHandler(clickToSelectEl, input);
+          }
         },
       );
     },
+    // Adds click event listener to the field card.
+    clickHandler(clickToSelectEl, input) {
+      $(clickToSelectEl).on('click', (event) => {
+        const clickToSelect = event.target.closest('.js-click-to-select');
+        this.selectHandler(clickToSelect, input);
+        $(input).trigger('updateOptions');
+      });
+    },
+    // Handles adding and removing classes for the different states.
+    selectHandler(clickToSelect, input) {
+      $(input).on('focus', () => clickToSelect.classList.add('focus'));
+      $(input).on('blur', () => clickToSelect.classList.remove('focus'));
+      input.checked = true;
+      document
+        .querySelectorAll('.js-click-to-select.selected')
+        .forEach((item) => {
+          item.classList.remove('selected');
+        });
+      clickToSelect.classList.add('selected');
+      // Ensure focus is added at the end of the process so wrap in
+      // a timeout.
+      setTimeout(() => {
+        // Remove the disabled attribute added by Drupal ajax so the
+        // element is focusable. This is safe as clicking the element
+        // multiple times causes no problems.
+        input.removeAttribute('disabled');
+        input.focus();
+      }, 0);
+    },
   };
 })(jQuery, Drupal, drupalSettings, Drupal.debounce);
diff --git a/core/modules/field_ui/src/Controller/FieldConfigAddController.php b/core/modules/field_ui/src/Controller/FieldConfigAddController.php
new file mode 100644
index 0000000000000000000000000000000000000000..2a5a8b81de48cd0dbe9090e6fdc832229a49023d
--- /dev/null
+++ b/core/modules/field_ui/src/Controller/FieldConfigAddController.php
@@ -0,0 +1,77 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\field_ui\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
+use Drupal\Core\Field\FieldTypePluginManagerInterface;
+use Drupal\Core\TempStore\PrivateTempStore;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Controller for building the field instance form.
+ *
+ * @internal
+ */
+final class FieldConfigAddController extends ControllerBase {
+
+  /**
+   * FieldConfigAddController constructor.
+   *
+   * @param \Drupal\Core\TempStore\PrivateTempStore $tempStore
+   *   The private tempstore.
+   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $fieldTypeManager
+   *   The field type plugin manager.
+   * @param \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $selectionManager
+   *   The entity reference selection plugin manager.
+   */
+  public function __construct(
+    protected readonly PrivateTempStore $tempStore,
+    protected readonly FieldTypePluginManagerInterface $fieldTypeManager,
+    protected readonly SelectionPluginManagerInterface $selectionManager,
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('tempstore.private')->get('field_ui'),
+      $container->get('plugin.manager.field.field_type'),
+      $container->get('plugin.manager.entity_reference_selection')
+    );
+  }
+
+  /**
+   * Builds the field config instance form.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $field_name
+   *   The name of the field to create.
+   *
+   * @return array
+   *   The field instance edit form.
+   */
+  public function fieldConfigAddConfigureForm(string $entity_type, string $field_name): array {
+    // @see \Drupal\field_ui\Form\FieldStorageAddForm::submitForm
+    $temp_storage = $this->tempStore->get($entity_type . ':' . $field_name);
+    if (!$temp_storage) {
+      throw new NotFoundHttpException();
+    }
+
+    /** @var \Drupal\Core\Field\FieldConfigInterface $entity */
+    $entity = $this->entityTypeManager()->getStorage('field_config')->create([
+      ...$temp_storage['field_config_values'],
+      'field_storage' => $temp_storage['field_storage'],
+    ]);
+
+    return $this->entityFormBuilder()->getForm($entity, 'default', [
+      'default_options' => $temp_storage['default_options'],
+    ]);
+  }
+
+}
diff --git a/core/modules/field_ui/src/Controller/FieldStorageAddController.php b/core/modules/field_ui/src/Controller/FieldStorageAddController.php
new file mode 100644
index 0000000000000000000000000000000000000000..c22ca0d94dd1010bef394bba13fa0abef692204d
--- /dev/null
+++ b/core/modules/field_ui/src/Controller/FieldStorageAddController.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\field_ui\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\TempStore\PrivateTempStore;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Controller for building the field storage form.
+ *
+ * @internal
+ *
+ * @todo remove in https://www.drupal.org/project/drupal/issues/3347291.
+ */
+final class FieldStorageAddController extends ControllerBase {
+
+  /**
+   * FieldStorageAddController constructor.
+   *
+   * @param \Drupal\Core\TempStore\PrivateTempStore $tempStore
+   *   The private tempstore.
+   */
+  public function __construct(protected readonly PrivateTempStore $tempStore) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('tempstore.private')->get('field_ui')
+    );
+  }
+
+  /**
+   * Builds the field storage form.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $field_name
+   *   The name of the field to create.
+   * @param string $bundle
+   *   The bundle where the field is being created.
+   *
+   * @return array
+   *   The field storage form.
+   */
+  public function storageAddConfigureForm(string $entity_type, string $field_name, string $bundle): array {
+    // @see \Drupal\field_ui\Form\FieldStorageAddForm::submitForm
+    $temp_storage = $this->tempStore->get($entity_type . ':' . $field_name);
+    if (!$temp_storage) {
+      throw new NotFoundHttpException();
+    }
+
+    return $this->entityFormBuilder()->getForm($temp_storage['field_storage'], 'edit', [
+      'entity_type_id' => $entity_type,
+      'bundle' => $bundle,
+    ]);
+  }
+
+}
diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php
index acd27872636dcf9383d1f49884c5e532375de2eb..f18f550c670a209fa62f1b902c1cb5d9549371e3 100644
--- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php
+++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php
@@ -2,13 +2,16 @@
 
 namespace Drupal\field_ui\Form;
 
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
 use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
 use Drupal\Core\Field\FieldFilteredMarkup;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
+use Drupal\Core\TempStore\PrivateTempStore;
 use Drupal\Core\TypedData\TypedDataInterface;
 use Drupal\Core\TypedData\TypedDataManagerInterface;
 use Drupal\Core\Url;
@@ -23,6 +26,8 @@
  */
 class FieldConfigEditForm extends EntityForm {
 
+  use FieldStorageCreationTrait;
+
   /**
    * The entity being used by this form.
    *
@@ -37,6 +42,20 @@ class FieldConfigEditForm extends EntityForm {
    */
   protected $entityTypeBundleInfo;
 
+  /**
+   * The name of the entity type.
+   *
+   * @var string
+   */
+  protected string $entityTypeId;
+
+  /**
+   * The entity bundle.
+   *
+   * @var string
+   */
+  protected string $bundle;
+
   /**
    * Constructs a new FieldConfigDeleteForm object.
    *
@@ -44,9 +63,25 @@ class FieldConfigEditForm extends EntityForm {
    *   The entity type bundle info service.
    * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager
    *   The type data manger.
+   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface|null $entityDisplayRepository
+   *   The entity display repository.
+   * @param \Drupal\Core\TempStore\PrivateTempStore|null $tempStore
+   *   The private tempstore.
    */
-  public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_info, protected TypedDataManagerInterface $typedDataManager) {
+  public function __construct(
+    EntityTypeBundleInfoInterface $entity_type_bundle_info,
+    protected TypedDataManagerInterface $typedDataManager,
+    protected ?EntityDisplayRepositoryInterface $entityDisplayRepository = NULL,
+    protected ?PrivateTempStore $tempStore = NULL) {
     $this->entityTypeBundleInfo = $entity_type_bundle_info;
+    if ($this->entityDisplayRepository === NULL) {
+      @trigger_error('Calling FieldConfigEditForm::__construct() without the $entityDisplayRepository argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3383771', E_USER_DEPRECATED);
+      $this->entityDisplayRepository = \Drupal::service('entity_display.repository');
+    }
+    if ($this->tempStore === NULL) {
+      @trigger_error('Calling FieldConfigEditForm::__construct() without the $tempStore argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3383771', E_USER_DEPRECATED);
+      $this->tempStore = \Drupal::service('tempstore.private')->get('field_ui');
+    }
   }
 
   /**
@@ -55,7 +90,9 @@ public function __construct(EntityTypeBundleInfoInterface $entity_type_bundle_in
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity_type.bundle.info'),
-      $container->get('typed_data_manager')
+      $container->get('typed_data_manager'),
+      $container->get('entity_display.repository'),
+      $container->get('tempstore.private')->get('field_ui')
     );
   }
 
@@ -261,7 +298,34 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function save(array $form, FormStateInterface $form_state) {
+    $temp_storage = $this->tempStore->get($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName());
+    if ($this->entity->isNew()) {
+      // @todo remove in https://www.drupal.org/project/drupal/issues/3347291.
+      if ($temp_storage && $temp_storage['field_storage']->isNew()) {
+        // Save field storage.
+        try {
+          $temp_storage['field_storage']->save();
+        }
+        catch (EntityStorageException $e) {
+          $this->tempStore->delete($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName());
+          $form_state->setRedirectUrl(FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle()));
+          $this->messenger()->addError($this->t('An error occurred while saving the field: @error', ['@error' => $e->getMessage()]));
+          return;
+        }
+      }
+    }
+    // Save field config.
     $this->entity->save();
+    if (isset($form_state->getStorage()['default_options'])) {
+      $default_options = $form_state->getStorage()['default_options'];
+      // Configure the default display modes.
+      $this->entityTypeId = $temp_storage['field_config_values']['entity_type'];
+      $this->bundle = $temp_storage['field_config_values']['bundle'];
+      $this->configureEntityFormDisplay($temp_storage['field_config_values']['field_name'], $default_options['entity_form_display'] ?? []);
+      $this->configureEntityViewDisplay($temp_storage['field_config_values']['field_name'], $default_options['entity_view_display'] ?? []);
+      // Delete the temp store entry.
+      $this->tempStore->delete($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName());
+    }
 
     $this->messenger()->addStatus($this->t('Saved %label configuration.', ['%label' => $this->entity->getLabel()]));
 
diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
index ed591d3ae03e43ce669b1d97145f44c590467b36..ea1f2f94436e4bfb087d1938b6e0b3c7e2f46e16 100644
--- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
@@ -5,7 +5,6 @@
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SortArray;
 use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Field\FallbackFieldTypeCategory;
@@ -13,6 +12,7 @@
 use Drupal\Core\Field\FieldTypePluginManagerInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\TempStore\PrivateTempStore;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\field_ui\FieldUI;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -24,8 +24,6 @@
  */
 class FieldStorageAddForm extends FormBase {
 
-  use FieldStorageCreationTrait;
-
   /**
    * The name of the entity type.
    *
@@ -54,13 +52,6 @@ class FieldStorageAddForm extends FormBase {
    */
   protected $entityFieldManager;
 
-  /**
-   * The entity display repository.
-   *
-   * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
-   */
-  protected $entityDisplayRepository;
-
   /**
    * The field type plugin manager.
    *
@@ -86,17 +77,20 @@ class FieldStorageAddForm extends FormBase {
    *   The configuration factory.
    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
    *   (optional) The entity field manager.
-   * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
-   *   (optional) The entity display repository.
+   * @param \Drupal\Core\TempStore\PrivateTempStore|null $tempStore
+   *   The private tempstore.
    * @param \Drupal\Core\Field\FieldTypeCategoryManagerInterface|null $fieldTypeCategoryManager
    *   The field type category plugin manager.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, ConfigFactoryInterface $config_factory, EntityFieldManagerInterface $entity_field_manager, EntityDisplayRepositoryInterface $entity_display_repository, protected ?FieldTypeCategoryManagerInterface $fieldTypeCategoryManager = NULL) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, ConfigFactoryInterface $config_factory, EntityFieldManagerInterface $entity_field_manager, protected ?PrivateTempStore $tempStore = NULL, protected ?FieldTypeCategoryManagerInterface $fieldTypeCategoryManager = NULL) {
     $this->entityTypeManager = $entity_type_manager;
     $this->fieldTypePluginManager = $field_type_plugin_manager;
     $this->configFactory = $config_factory;
     $this->entityFieldManager = $entity_field_manager;
-    $this->entityDisplayRepository = $entity_display_repository;
+    if ($this->tempStore === NULL) {
+      @trigger_error('Calling FieldStorageAddForm::__construct() without the $tempStore argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3383719', E_USER_DEPRECATED);
+      $this->tempStore = \Drupal::service('tempstore.private')->get('field_ui');
+    }
     if ($this->fieldTypeCategoryManager === NULL) {
       @trigger_error('Calling FieldStorageAddForm::__construct() without the $fieldTypeCategoryManager argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3375740', E_USER_DEPRECATED);
       $this->fieldTypeCategoryManager = \Drupal::service('plugin.manager.field.field_type_category');
@@ -119,7 +113,7 @@ public static function create(ContainerInterface $container) {
       $container->get('plugin.manager.field.field_type'),
       $container->get('config.factory'),
       $container->get('entity_field.manager'),
-      $container->get('entity_display.repository'),
+      $container->get('tempstore.private')->get('field_ui'),
       $container->get('plugin.manager.field.field_type_category'),
     );
   }
@@ -157,12 +151,13 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
 
     $field_type_options = $unique_definitions = [];
     $grouped_definitions = $this->fieldTypePluginManager->getGroupedDefinitions($this->fieldTypePluginManager->getUiDefinitions(), 'label', 'id');
+    $category_definitions = $this->fieldTypeCategoryManager->getDefinitions();
     // Invoke a hook to get category properties.
     foreach ($grouped_definitions as $category => $field_types) {
       foreach ($field_types as $name => $field_type) {
         $unique_definitions[$category][$name] = ['unique_identifier' => $name] + $field_type;
         if ($this->fieldTypeCategoryManager->hasDefinition($category)) {
-          $category_plugin = $this->fieldTypeCategoryManager->createInstance($category, $unique_definitions[$category][$name]);
+          $category_plugin = $this->fieldTypeCategoryManager->createInstance($category, $unique_definitions[$category][$name], $category_definitions[$category]);
           $field_type_options[$category_plugin->getPluginId()] = ['unique_identifier' => $name] + $field_type;
         }
         else {
@@ -243,6 +238,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
           '#variant' => 'field-option',
         ],
       ];
+
+      if ($libraries = $category_info->getLibraries()) {
+        $field_type_options_radios[$id]['#attached']['library'] = $libraries;
+      }
     }
     uasort($field_type_options_radios, [SortArray::class, 'sortByWeightProperty']);
     $form['add']['new_storage_type'] = $field_type_options_radios;
@@ -344,7 +343,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
     $form['actions'] = ['#type' => 'actions'];
     $form['actions']['submit'] = [
       '#type' => 'submit',
-      '#value' => $this->t('Save and continue'),
+      '#value' => $this->t('Continue'),
       '#button_type' => 'primary',
     ];
 
@@ -416,6 +415,8 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       'entity_type' => $this->entityTypeId,
       'bundle' => $this->bundle,
     ];
+    $default_options = [];
+
     // Check if we're dealing with a preconfigured field.
     if (strpos($field_storage_type, 'field_ui:') === 0) {
       [, $field_type, $preset_key] = explode(':', $field_storage_type, 3);
@@ -441,40 +442,32 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     ];
 
     try {
-      // Create the field storage.
-      $this->entityTypeManager->getStorage('field_storage_config')
-        ->create($field_storage_values)->save();
-
-      // Create the field.
-      $field = $this->entityTypeManager->getStorage('field_config')
-        ->create($field_values);
-      $field->save();
-
-      // Configure the display modes.
-      $this->configureEntityFormDisplay($field_name, $default_options['entity_form_display'] ?? []);
-      $this->configureEntityViewDisplay($field_name, $default_options['entity_view_display'] ?? []);
+      $field_storage_entity = $this->entityTypeManager->getStorage('field_storage_config')->create($field_storage_values);
     }
     catch (\Exception $e) {
-      $this->messenger()->addError($this->t(
-        'There was a problem creating field %label: @message',
-        ['%label' => $values['label'], '@message' => $e->getMessage()]));
+      $this->messenger()->addError($this->t('There was a problem creating field %label: @message', ['%label' => $values['label'], '@message' => $e->getMessage()]));
       return;
     }
 
+    // Save field and field storage values in tempstore.
+    $this->tempStore->set($this->entityTypeId . ':' . $field_name, [
+      'field_storage' => $field_storage_entity,
+      'field_config_values' => $field_values,
+      'default_options' => $default_options,
+    ]);
+
     // Configure next steps in the multi-part form.
     $destinations = [];
     $route_parameters = [
-      'field_config' => $field->id(),
+      'entity_type' => $this->entityTypeId,
+      'field_name' => $field_name,
     ] + FieldUI::getRouteBundleParameter($entity_type, $this->bundle);
-    // Always show the field settings step, as the cardinality needs to be
-    // configured for new fields.
     $destinations[] = [
-      'route_name' => "entity.field_config.{$this->entityTypeId}_storage_edit_form",
+      'route_name' => "field_ui.field_storage_add_{$this->entityTypeId}",
       'route_parameters' => $route_parameters,
     ];
-
     $destinations[] = [
-      'route_name' => "entity.field_config.{$this->entityTypeId}_field_edit_form",
+      'route_name' => "field_ui.field_add_{$this->entityTypeId}",
       'route_parameters' => $route_parameters,
     ];
     $destinations[] = [
@@ -489,8 +482,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     // Store new field information for any additional submit handlers.
     $form_state->set(['fields_added', '_add_new_field'], $field_name);
-
-    $this->messenger()->addMessage($this->t('Your settings have been saved.'));
   }
 
   /**
diff --git a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
index eeb85e2259fd5e6ce76a0f6c4843a2fe28c35f66..f83c7d3e9e75d1a1608a191b0486bed6843449b3 100644
--- a/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
@@ -3,11 +3,15 @@
 namespace Drupal\field_ui\Form;
 
 use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\TempStore\PrivateTempStore;
+use Drupal\Core\TypedData\TypedDataManagerInterface;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field_ui\FieldUI;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
@@ -24,6 +28,34 @@ class FieldStorageConfigEditForm extends EntityForm {
    */
   protected $entity;
 
+  /**
+   * FieldStorageConfigEditForm constructor.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager
+   *   The typed data manager.
+   * @param \Drupal\Core\TempStore\PrivateTempStore|null $tempStore
+   *   The private tempstore.
+   */
+  public function __construct(
+    protected TypedDataManagerInterface $typedDataManager,
+    protected ?PrivateTempStore $tempStore = NULL,
+  ) {
+    if ($this->tempStore === NULL) {
+      @trigger_error('Calling FieldStorageConfigEditForm::__construct() without the $tempStore argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3383720', E_USER_DEPRECATED);
+      $this->tempStore = \Drupal::service('tempstore.private')->get('field_ui');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('typed_data_manager'),
+      $container->get('tempstore.private')->get('field_ui')
+    );
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -64,9 +96,10 @@ public function buildForm(array $form, FormStateInterface $form_state, $field_co
    * {@inheritdoc}
    */
   public function form(array $form, FormStateInterface $form_state) {
+    $temp_storage = $this->tempStore->get($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName());
     $form = parent::form($form, $form_state);
 
-    $field_label = $form_state->get('field_config')->label();
+    $field_label = $this->entity->isNew() ? $temp_storage['field_config_values']['label'] : $form_state->get('field_config')->label();
     $form['#title'] = $field_label;
     $form['#prefix'] = '<p>' . $this->t('These settings apply to the %field field everywhere it is used. Some also impact the way that data is stored and cannot be changed once data has been created.', ['%field' => $field_label]) . '</p>';
 
@@ -85,7 +118,18 @@ public function form(array $form, FormStateInterface $form_state) {
       'entity_id' => NULL,
     ];
     $entity = _field_create_entity_from_ids($ids);
-    $items = $entity->get($this->entity->getName());
+    if (!$this->entity->isNew()) {
+      $items = $entity->get($this->entity->getName());
+    }
+    else {
+      // Create a temporary field config so that we can access the field
+      // definition.
+      $field_config = $this->entityTypeManager->getStorage('field_config')->create([
+        ...$temp_storage['field_config_values'],
+        'field_storage' => $temp_storage['field_storage'],
+      ]);
+      $items = $this->typedDataManager->create($field_config, name: $this->entity->getName(), parent: EntityAdapter::createFromEntity($entity));
+    }
     $item = $items->first() ?: $items->appendItem();
     $form['settings'] += $item->storageSettingsForm($form, $form_state, $this->entity->hasData());
 
@@ -164,7 +208,7 @@ protected function getCardinalityForm() {
    */
   protected function actions(array $form, FormStateInterface $form_state) {
     $elements = parent::actions($form, $form_state);
-    $elements['submit']['#value'] = $this->t('Save field settings');
+    $elements['submit']['#value'] = $this->entity->isNew() ? $this->t('Continue') : $this->t('Save');
 
     return $elements;
   }
@@ -218,10 +262,19 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function save(array $form, FormStateInterface $form_state) {
-    $field_label = $form_state->get('field_config')->label();
+    // Save field storage entity values in tempstore.
+    if ($this->entity->isNew()) {
+      $temp_storage = $this->tempStore->get($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName());
+      $field_label = $temp_storage['field_config_values']['label'];
+      $temp_storage['field_storage'] = $this->entity;
+      $this->tempStore->set($this->entity->getTargetEntityTypeId() . ':' . $this->entity->getName(), $temp_storage);
+    }
     try {
-      $this->entity->save();
-      $this->messenger()->addStatus($this->t('Updated field %label field settings.', ['%label' => $field_label]));
+      if (!$this->entity->isNew()) {
+        $field_label = $form_state->get('field_config')->label();
+        $this->entity->save();
+        $this->messenger()->addMessage($this->t('Your settings have been saved.'));
+      }
       $request = $this->getRequest();
       if (($destinations = $request->query->all('destinations')) && $next_destination = FieldUI::getNextDestination($destinations)) {
         $request->query->remove('destinations');
diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php
index a84dde318179bd23d3906abbff0a551157e1f211..affe1ea2c732b0a6d8e7be83f107ce5839d99d8d 100644
--- a/core/modules/field_ui/src/Routing/RouteSubscriber.php
+++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php
@@ -5,6 +5,8 @@
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Routing\RouteSubscriberBase;
 use Drupal\Core\Routing\RoutingEvents;
+use Drupal\field_ui\Controller\FieldConfigAddController;
+use Drupal\field_ui\Controller\FieldStorageAddController;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
 
@@ -109,6 +111,29 @@ protected function alterRoutes(RouteCollection $collection) {
         );
         $collection->add("field_ui.field_storage_config_add_$entity_type_id", $route);
 
+        $route = new Route(
+          "$path/add-field/{entity_type}/{field_name}",
+          [
+            '_controller' => FieldConfigAddController::class . '::fieldConfigAddConfigureForm',
+            '_title' => 'Add field',
+          ] + $defaults,
+          ['_permission' => 'administer ' . $entity_type_id . ' fields'],
+          $options
+        );
+        $collection->add("field_ui.field_add_$entity_type_id", $route);
+
+        // @todo remove in https://www.drupal.org/project/drupal/issues/3347291.
+        $route = new Route(
+          "$path/add-storage/{entity_type}/{field_name}",
+          [
+            '_controller' => FieldStorageAddController::class . '::storageAddConfigureForm',
+            '_title' => 'Add storage',
+          ] + $defaults,
+          ['_permission' => 'administer ' . $entity_type_id . ' fields'],
+          $options
+        );
+        $collection->add("field_ui.field_storage_add_$entity_type_id", $route);
+
         $route = new Route(
           "$path/fields/reuse",
           [
diff --git a/core/modules/field_ui/tests/src/Functional/FieldTypeCategoriesIntegrationTest.php b/core/modules/field_ui/tests/src/Functional/FieldTypeCategoriesIntegrationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1490c2e8410b44b9b0e20dee366f368b77682ddb
--- /dev/null
+++ b/core/modules/field_ui/tests/src/Functional/FieldTypeCategoriesIntegrationTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\Tests\field_ui\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests field UI integration with field type categories for loading libraries.
+ *
+ * @group field_ui
+ */
+class FieldTypeCategoriesIntegrationTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'node',
+    'file',
+    'field_ui',
+    'options',
+    'comment',
+    'link',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    // Create a test user.
+    $admin_user = $this->drupalCreateUser(['administer node fields']);
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Tests if the libraries are loaded on FieldStorageAddForm.
+   */
+  public function testLibrariesLoaded() {
+    $this->drupalGet('admin/structure/types/manage/' . $this->drupalCreateContentType()->id() . '/fields/add-field');
+    $page_content = $this->getSession()->getPage()->getContent();
+    $css_libraries = [
+      'drupal.file-icon',
+      'drupal.text-icon',
+      'drupal.options-icon',
+      'drupal.comment-icon',
+      'drupal.link-icon',
+    ];
+    foreach ($css_libraries as $css_library) {
+      // Check if the library asset is present in the rendered HTML.
+      $this->assertStringContainsString($css_library, $page_content);
+    }
+  }
+
+}
diff --git a/core/modules/field_ui/tests/src/Functional/GenericTest.php b/core/modules/field_ui/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..394f61226362328c04f412668f9a98883fd256e0
--- /dev/null
+++ b/core/modules/field_ui/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\field_ui\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for field_ui.
+ *
+ * @group field_ui
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/field_ui/tests/src/Functional/ManageDisplayTest.php b/core/modules/field_ui/tests/src/Functional/ManageDisplayTest.php
index 254704993e4048371bf12b4a97e09ea311c4692d..910b33f041b3a10d796634520b847b7e822e9a0b 100644
--- a/core/modules/field_ui/tests/src/Functional/ManageDisplayTest.php
+++ b/core/modules/field_ui/tests/src/Functional/ManageDisplayTest.php
@@ -17,6 +17,7 @@
  * Tests the Field UI "Manage display" and "Manage form display" screens.
  *
  * @group field_ui
+ * @group #slow
  */
 class ManageDisplayTest extends BrowserTestBase {
 
diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php
index 349aa8a69184bdeb44541842200ad47cf1d2623c..5f43b4608b62a35fa4849d2d34f4dbd474966294 100644
--- a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php
+++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php
@@ -3,153 +3,17 @@
 namespace Drupal\Tests\field_ui\Functional;
 
 use Behat\Mink\Exception\ElementNotFoundException;
-use Drupal\Core\Entity\Entity\EntityFormDisplay;
-use Drupal\Core\Entity\Entity\EntityFormMode;
-use Drupal\Core\Entity\Entity\EntityViewDisplay;
-use Drupal\Core\Entity\Entity\EntityViewMode;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\node\Entity\NodeType;
-use Drupal\taxonomy\Entity\Vocabulary;
-use Drupal\Tests\BrowserTestBase;
-use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
-use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
 
 /**
  * Tests the Field UI "Manage fields" screen.
  *
  * @group field_ui
+ * @group #slow
  */
-class ManageFieldsFunctionalTest extends BrowserTestBase {
-
-  use FieldUiTestTrait;
-  use EntityReferenceTestTrait;
-
-  /**
-   * Modules to install.
-   *
-   * @var array
-   */
-  protected static $modules = [
-    'node',
-    'field_ui',
-    'field_test',
-    'taxonomy',
-    'image',
-    'block',
-    'node_access_test',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $defaultTheme = 'stark';
-
-  /**
-   * The ID of the custom content type created for testing.
-   *
-   * @var string
-   */
-  protected $contentType;
-
-  /**
-   * The label for a random field to be created for testing.
-   *
-   * @var string
-   */
-  protected $fieldLabel;
-
-  /**
-   * The input name of a random field to be created for testing.
-   *
-   * @var string
-   */
-  protected $fieldNameInput;
-
-  /**
-   * The name of a random field to be created for testing.
-   *
-   * @var string
-   */
-  protected $fieldName;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    $this->drupalPlaceBlock('system_breadcrumb_block');
-    $this->drupalPlaceBlock('local_actions_block');
-    $this->drupalPlaceBlock('local_tasks_block');
-    $this->drupalPlaceBlock('page_title_block');
-
-    // Create a test user.
-    $admin_user = $this->drupalCreateUser([
-      'access content',
-      'administer content types',
-      'bypass node access',
-      'administer node fields',
-      'administer node form display',
-      'administer node display',
-      'administer taxonomy',
-      'administer taxonomy_term fields',
-      'administer taxonomy_term display',
-      'administer users',
-      'administer account settings',
-      'administer user display',
-    ]);
-    $this->drupalLogin($admin_user);
-
-    // Create content type, with underscores.
-    $type_name = $this->randomMachineName(8) . '_test';
-    $type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
-    $this->contentType = $type->id();
-
-    // Create random field name with markup to test escaping.
-    $this->fieldLabel = '<em>' . $this->randomMachineName(8) . '</em>';
-    $this->fieldNameInput = $this->randomMachineName(8);
-    $this->fieldName = 'field_' . $this->fieldNameInput;
-
-    // Create Basic page and Article node types.
-    $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
-    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
-
-    // Create a vocabulary named "Tags".
-    $vocabulary = Vocabulary::create([
-      'name' => 'Tags',
-      'vid' => 'tags',
-      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
-    ]);
-    $vocabulary->save();
-
-    // Create a vocabulary named "Kittens".
-    Vocabulary::create([
-      'name' => 'Kittens',
-      'vid' => 'kittens',
-      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
-    ])->save();
-
-    $handler_settings = [
-      'target_bundles' => [
-        $vocabulary->id() => $vocabulary->id(),
-      ],
-    ];
-    $this->createEntityReferenceField('node', 'article', 'field_' . $vocabulary->id(), 'Tags', 'taxonomy_term', 'default', $handler_settings);
-
-    \Drupal::service('entity_display.repository')
-      ->getFormDisplay('node', 'article')
-      ->setComponent('field_' . $vocabulary->id())
-      ->save();
-
-    // Setup node access testing.
-    node_access_rebuild();
-    node_access_test_add_field(NodeType::load('article'));
-    \Drupal::state()->set('node_access_test.private', TRUE);
-
-  }
+class ManageFieldsFunctionalTest extends ManageFieldsFunctionalTestBase {
 
   /**
    * Runs the field CRUD tests.
@@ -174,7 +38,7 @@ public function testCRUDFields() {
    * @param string $type
    *   (optional) The name of a content type.
    */
-  public function manageFieldsPage($type = '') {
+  protected function manageFieldsPage($type = '') {
     $type = empty($type) ? $this->contentType : $type;
     $this->drupalGet('admin/structure/types/manage/' . $type . '/fields');
     // Check all table columns.
@@ -221,7 +85,7 @@ public function manageFieldsPage($type = '') {
    * @todo Assert properties can be set in the form and read back in
    * $field_storage and $fields.
    */
-  public function createField() {
+  protected function createField() {
     // Create a test field.
     $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
   }
@@ -229,7 +93,7 @@ public function createField() {
   /**
    * Tests editing an existing field.
    */
-  public function updateField() {
+  protected function updateField() {
     $field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
     // Go to the field edit page.
     $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage');
@@ -240,7 +104,7 @@ public function updateField() {
     $edit = [
       'settings[test_field_storage_setting]' => $string,
     ];
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
 
     // Go to the field edit page.
     $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
@@ -260,7 +124,7 @@ public function updateField() {
   /**
    * Tests adding an existing field in another content type.
    */
-  public function addExistingField() {
+  protected function addExistingField() {
     // Check "Re-use existing field" appears.
     $this->drupalGet('admin/structure/types/manage/page/fields');
     $this->assertSession()->pageTextContains('Re-use an existing field');
@@ -281,7 +145,7 @@ public function addExistingField() {
    * We do not test if the number can be submitted with anything else than a
    * numeric value. That is tested already in FormTest::testNumber().
    */
-  public function cardinalitySettings() {
+  protected function cardinalitySettings() {
     $field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body/storage';
 
     // Assert the cardinality other field cannot be empty when cardinality is
@@ -291,7 +155,7 @@ public function cardinalitySettings() {
       'cardinality_number' => '',
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->assertSession()->pageTextContains('Number of values is required.');
 
     // Submit a custom number.
@@ -300,8 +164,7 @@ public function cardinalitySettings() {
       'cardinality_number' => 6,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
-    $this->assertSession()->pageTextContains('Updated field Body field settings.');
+    $this->submitForm($edit, 'Save');
     $this->drupalGet($field_edit_path);
     $this->assertSession()->fieldValueEquals('cardinality', 'number');
     $this->assertSession()->fieldValueEquals('cardinality_number', 6);
@@ -324,7 +187,7 @@ public function cardinalitySettings() {
       'cardinality_number' => 1,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->assertSession()->pageTextContains("There is 1 entity with 2 or more values in this field");
 
     // Create a second entity with three values.
@@ -337,8 +200,7 @@ public function cardinalitySettings() {
       'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
-    $this->assertSession()->pageTextContains('Updated field Body field settings.');
+    $this->submitForm($edit, 'Save');
     $this->drupalGet($field_edit_path);
     $this->assertSession()->fieldValueEquals('cardinality', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
     $this->assertSession()->fieldValueEquals('cardinality_number', 1);
@@ -350,7 +212,7 @@ public function cardinalitySettings() {
       'cardinality_number' => 1,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->assertSession()->pageTextContains("There are 2 entities with 2 or more values in this field");
 
     $edit = [
@@ -358,7 +220,7 @@ public function cardinalitySettings() {
       'cardinality_number' => 2,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->assertSession()->pageTextContains("There is 1 entity with 3 or more values in this field");
 
     $edit = [
@@ -366,7 +228,7 @@ public function cardinalitySettings() {
       'cardinality_number' => 3,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
 
     // Test the cardinality validation is not access sensitive.
 
@@ -375,7 +237,7 @@ public function cardinalitySettings() {
       'cardinality' => (string) FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $node = $this->drupalCreateNode([
       'private' => TRUE,
       'uid' => 0,
@@ -396,21 +258,21 @@ public function cardinalitySettings() {
       'cardinality_number' => 2,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->assertSession()->pageTextContains("There are 2 entities with 3 or more values in this field");
     $edit = [
       'cardinality' => 'number',
       'cardinality_number' => 3,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $this->assertSession()->pageTextContains("There is 1 entity with 4 or more values in this field");
     $edit = [
       'cardinality' => 'number',
       'cardinality_number' => 4,
     ];
     $this->drupalGet($field_edit_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
   }
 
   /**
@@ -462,7 +324,7 @@ protected function addPersistentFieldStorage() {
    *
    * @internal
    */
-  public function assertFieldSettings(string $bundle, string $field_name, string $string = 'dummy test string', string $entity_type = 'node'): void {
+  protected function assertFieldSettings(string $bundle, string $field_name, string $string = 'dummy test string', string $entity_type = 'node'): void {
     // Assert field storage settings.
     $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
     $this->assertSame($string, $field_storage->getSetting('test_field_storage_setting'), 'Field storage settings were found.');
@@ -490,7 +352,7 @@ public function testFieldPrefix() {
       'field_name' => $field_exceed_max_length_input,
     ];
     $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field');
-    $this->submitForm($edit, 'Save and continue');
+    $this->submitForm($edit, 'Continue');
     $this->assertSession()->pageTextContains('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.');
 
     // Create a valid field.
@@ -642,14 +504,14 @@ public function testDisallowedFieldNames() {
     $edit['field_name'] = 'title';
     $bundle_path = 'admin/structure/types/manage/' . $this->contentType;
     $this->drupalGet("{$bundle_path}/fields/add-field");
-    $this->submitForm($edit, 'Save and continue');
+    $this->submitForm($edit, 'Continue');
     $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
 
     // Try with a base field.
     $edit['field_name'] = 'sticky';
     $bundle_path = 'admin/structure/types/manage/' . $this->contentType;
     $this->drupalGet("{$bundle_path}/fields/add-field");
-    $this->submitForm($edit, 'Save and continue');
+    $this->submitForm($edit, 'Continue');
     $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
   }
 
@@ -755,11 +617,17 @@ public function testHiddenFields() {
   public function testDuplicateFieldName() {
     // field_tags already exists, so we're expecting an error when trying to
     // create a new field with the same name.
-    $url = 'admin/structure/types/manage/' . $this->contentType;
-    $this->fieldUIAddNewField($url, 'tags', $this->randomMachineName(), 'entity_reference', [], [], FALSE);
+    $url = 'admin/structure/types/manage/' . $this->contentType . '/fields/add-field';
+    $this->drupalGet($url);
+    $edit = [
+      'label' => $this->randomMachineName(),
+      'field_name' => 'tags',
+      'new_storage_type' => 'boolean',
+    ];
+    $this->submitForm($edit, 'Continue');
 
     $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
-    $this->assertSession()->addressEquals($url . '/fields/add-field');
+    $this->assertSession()->addressEquals($url);
   }
 
   /**
@@ -770,7 +638,7 @@ public function testExternalDestinations() {
       'query' => ['destinations' => ['http://example.com']],
     ];
     $this->drupalGet('admin/structure/types/manage/article/fields/node.article.body/storage', $options);
-    $this->submitForm([], 'Save field settings');
+    $this->submitForm([], 'Save');
     // The external redirect should not fire.
     $this->assertSession()->addressEquals('admin/structure/types/manage/article/fields/node.article.body/storage?destinations%5B0%5D=http%3A//example.com');
     $this->assertSession()->statusCodeEquals(200);
@@ -895,155 +763,6 @@ public function testNonExistentFieldUrls() {
     $this->assertSession()->statusCodeEquals(404);
   }
 
-  /**
-   * Tests that options are copied over when reusing a field.
-   *
-   * @dataProvider entityTypesProvider
-   */
-  public function testReuseField($entity_type, $bundle1, $bundle2) {
-    $field_name = 'test_reuse';
-    $label = $this->randomMachineName();
-
-    // Create field with pre-configured options.
-    $this->drupalGet($bundle1['path'] . "/fields/add-field");
-    $this->fieldUIAddNewField(NULL, $field_name, $label, 'field_ui:test_field_with_preconfigured_options:custom_options');
-    $new_label = $this->randomMachineName();
-    $this->fieldUIAddExistingField($bundle2['path'], "field_{$field_name}", $new_label);
-    $field = FieldConfig::loadByName($entity_type, $bundle2['id'], "field_{$field_name}");
-    $this->assertTrue($field->isRequired());
-    $this->assertEquals($new_label, $field->label());
-    $this->assertEquals('preconfigured_field_setting', $field->getSetting('test_field_setting'));
-
-    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
-    $display_repository = \Drupal::service('entity_display.repository');
-
-    $form_display = $display_repository->getFormDisplay($entity_type, $bundle2['id']);
-    $this->assertEquals('test_field_widget_multiple', $form_display->getComponent("field_{$field_name}")['type']);
-    $view_display = $display_repository->getViewDisplay($entity_type, $bundle2['id']);
-    $this->assertEquals('field_test_multiple', $view_display->getComponent("field_{$field_name}")['type']);
-    $this->assertEquals('altered dummy test string', $view_display->getComponent("field_{$field_name}")['settings']['test_formatter_setting_multiple']);
-  }
-
-  /**
-   * Tests that options are copied over when reusing a field.
-   *
-   * @dataProvider entityTypesProvider
-   */
-  public function testReuseFieldMultipleDisplay($entity_type, $bundle1, $bundle2) {
-    // Create additional form mode and enable it on both bundles.
-    EntityFormMode::create([
-      'id' => "{$entity_type}.little",
-      'label' => 'Little Form',
-      'targetEntityType' => $entity_type,
-    ])->save();
-    $form_display = EntityFormDisplay::create([
-      'id' => "{$entity_type}.{$bundle1['id']}.little",
-      'targetEntityType' => $entity_type,
-      'status' => TRUE,
-      'bundle' => $bundle1['id'],
-      'mode' => 'little',
-    ]);
-    $form_display->save();
-    EntityFormDisplay::create([
-      'id' => "{$entity_type}.{$bundle2['id']}.little",
-      'targetEntityType' => $entity_type,
-      'status' => TRUE,
-      'bundle' => $bundle2['id'],
-      'mode' => 'little',
-    ])->save();
-
-    // Create additional view mode and enable it on both bundles.
-    EntityViewMode::create([
-      'id' => "{$entity_type}.little",
-      'targetEntityType' => $entity_type,
-      'status' => TRUE,
-      'enabled' => TRUE,
-      'label' => 'Little View Mode',
-    ])->save();
-    $view_display = EntityViewDisplay::create([
-      'id' => "{$entity_type}.{$bundle1['id']}.little",
-      'targetEntityType' => $entity_type,
-      'status' => TRUE,
-      'bundle' => $bundle1['id'],
-      'mode' => 'little',
-    ]);
-    $view_display->save();
-    EntityViewDisplay::create([
-      'id' => "{$entity_type}.{$bundle2['id']}.little",
-      'targetEntityType' => $entity_type,
-      'status' => TRUE,
-      'bundle' => $bundle2['id'],
-      'mode' => 'little',
-    ])->save();
-
-    $field_name = 'test_reuse';
-    $label = $this->randomMachineName();
-
-    // Create field with pre-configured options.
-    $this->drupalGet($bundle1['path'] . "/fields/add-field");
-    $this->fieldUIAddNewField(NULL, $field_name, $label, 'field_ui:test_field_with_preconfigured_options:custom_options');
-    $view_display->setComponent("field_{$field_name}", [
-      'type' => 'field_test_default',
-      'region' => 'content',
-    ])->save();
-    $form_display->setComponent("field_{$field_name}", [
-      'type' => 'test_field_widget',
-      'region' => 'content',
-    ])->save();
-
-    $new_label = $this->randomMachineName();
-    $this->fieldUIAddExistingField($bundle2['path'], "field_{$field_name}", $new_label);
-
-    $field = FieldConfig::loadByName($entity_type, $bundle2['id'], "field_{$field_name}");
-    $this->assertTrue($field->isRequired());
-    $this->assertEquals($new_label, $field->label());
-    $this->assertEquals('preconfigured_field_setting', $field->getSetting('test_field_setting'));
-
-    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
-    $display_repository = \Drupal::service('entity_display.repository');
-
-    // Ensure that the additional form display has correct settings.
-    $form_display = $display_repository->getFormDisplay($entity_type, $bundle2['id'], $form_display->getMode());
-    $this->assertEquals('test_field_widget', $form_display->getComponent("field_{$field_name}")['type']);
-
-    // Ensure that the additional view display has correct settings.
-    $view_display = $display_repository->getViewDisplay($entity_type, $bundle2['id'], $view_display->getMode());
-    $this->assertEquals('field_test_default', $view_display->getComponent("field_{$field_name}")['type']);
-  }
-
-  /**
-   * Data provider for testing Field UI with multiple entity types.
-   *
-   * @return array
-   *   Test cases.
-   */
-  public function entityTypesProvider() {
-    return [
-      'node' => [
-        'entity_type' => 'node',
-        'article' => [
-          'id' => 'article',
-          'path' => 'admin/structure/types/manage/article',
-        ],
-        'page' => [
-          'id' => 'page',
-          'path' => 'admin/structure/types/manage/page',
-        ],
-      ],
-      'taxonomy' => [
-        'entity_type' => 'taxonomy_term',
-        'tags' => [
-          'id' => 'tags',
-          'path' => 'admin/structure/taxonomy/manage/tags/overview',
-        ],
-        'kittens' => [
-          'id' => 'kittens',
-          'path' => 'admin/structure/taxonomy/manage/kittens/overview',
-        ],
-      ],
-    ];
-  }
-
   /**
    * Test translation defaults.
    */
diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..f6aa76cfd779a245c73f53fe99ba040e8a66d771
--- /dev/null
+++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php
@@ -0,0 +1,144 @@
+<?php
+
+namespace Drupal\Tests\field_ui\Functional;
+
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\node\Entity\NodeType;
+use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
+use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
+
+/**
+ * Tests the Field UI "Manage fields" screen.
+ */
+class ManageFieldsFunctionalTestBase extends BrowserTestBase {
+
+  use FieldUiTestTrait;
+  use EntityReferenceTestTrait;
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  protected static $modules = [
+    'node',
+    'field_ui',
+    'field_test',
+    'taxonomy',
+    'image',
+    'block',
+    'node_access_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * The ID of the custom content type created for testing.
+   *
+   * @var string
+   */
+  protected $contentType;
+
+  /**
+   * The label for a random field to be created for testing.
+   *
+   * @var string
+   */
+  protected $fieldLabel;
+
+  /**
+   * The input name of a random field to be created for testing.
+   *
+   * @var string
+   */
+  protected $fieldNameInput;
+
+  /**
+   * The name of a random field to be created for testing.
+   *
+   * @var string
+   */
+  protected $fieldName;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->drupalPlaceBlock('system_breadcrumb_block');
+    $this->drupalPlaceBlock('local_actions_block');
+    $this->drupalPlaceBlock('local_tasks_block');
+    $this->drupalPlaceBlock('page_title_block');
+
+    // Create a test user.
+    $admin_user = $this->drupalCreateUser([
+      'access content',
+      'administer content types',
+      'bypass node access',
+      'administer node fields',
+      'administer node form display',
+      'administer node display',
+      'administer taxonomy',
+      'administer taxonomy_term fields',
+      'administer taxonomy_term display',
+      'administer users',
+      'administer account settings',
+      'administer user display',
+    ]);
+    $this->drupalLogin($admin_user);
+
+    // Create content type, with underscores.
+    $type_name = $this->randomMachineName(8) . '_test';
+    $type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
+    $this->contentType = $type->id();
+
+    // Create random field name with markup to test escaping.
+    $this->fieldLabel = '<em>' . $this->randomMachineName(8) . '</em>';
+    $this->fieldNameInput = $this->randomMachineName(8);
+    $this->fieldName = 'field_' . $this->fieldNameInput;
+
+    // Create Basic page and Article node types.
+    $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
+    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
+
+    // Create a vocabulary named "Tags".
+    $vocabulary = Vocabulary::create([
+      'name' => 'Tags',
+      'vid' => 'tags',
+      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
+    ]);
+    $vocabulary->save();
+
+    // Create a vocabulary named "Kittens".
+    Vocabulary::create([
+      'name' => 'Kittens',
+      'vid' => 'kittens',
+      'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
+    ])->save();
+
+    $handler_settings = [
+      'target_bundles' => [
+        $vocabulary->id() => $vocabulary->id(),
+      ],
+    ];
+    $this->createEntityReferenceField('node', 'article', 'field_' . $vocabulary->id(), 'Tags', 'taxonomy_term', 'default', $handler_settings);
+
+    \Drupal::service('entity_display.repository')
+      ->getFormDisplay('node', 'article')
+      ->setComponent('field_' . $vocabulary->id())
+      ->save();
+
+    // Setup node access testing.
+    node_access_rebuild();
+    node_access_test_add_field(NodeType::load('article'));
+    \Drupal::state()->set('node_access_test.private', TRUE);
+
+  }
+
+}
diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsMultipleTypesTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsMultipleTypesTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..82ae6a6ded76772a56a37b890c15c5732c555419
--- /dev/null
+++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsMultipleTypesTest.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Drupal\Tests\field_ui\Functional;
+
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\Entity\EntityFormMode;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\Entity\Entity\EntityViewMode;
+use Drupal\field\Entity\FieldConfig;
+
+/**
+ * Tests the Field UI "Manage fields" screen.
+ *
+ * @group field_ui
+ * @group #slow
+ */
+class ManageFieldsMultipleTypesTest extends ManageFieldsFunctionalTestBase {
+
+  /**
+   * Tests that options are copied over when reusing a field.
+   *
+   * @dataProvider entityTypesProvider
+   */
+  public function testReuseField($entity_type, $bundle1, $bundle2) {
+    $field_name = 'test_reuse';
+    $label = $this->randomMachineName();
+
+    // Create field with pre-configured options.
+    $this->drupalGet($bundle1['path'] . "/fields/add-field");
+    $this->fieldUIAddNewField(NULL, $field_name, $label, 'field_ui:test_field_with_preconfigured_options:custom_options');
+    $new_label = $this->randomMachineName();
+    $this->fieldUIAddExistingField($bundle2['path'], "field_{$field_name}", $new_label);
+    $field = FieldConfig::loadByName($entity_type, $bundle2['id'], "field_{$field_name}");
+    $this->assertTrue($field->isRequired());
+    $this->assertEquals($new_label, $field->label());
+    $this->assertEquals('preconfigured_field_setting', $field->getSetting('test_field_setting'));
+
+    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
+    $display_repository = \Drupal::service('entity_display.repository');
+
+    $form_display = $display_repository->getFormDisplay($entity_type, $bundle2['id']);
+    $this->assertEquals('test_field_widget_multiple', $form_display->getComponent("field_{$field_name}")['type']);
+    $view_display = $display_repository->getViewDisplay($entity_type, $bundle2['id']);
+    $this->assertEquals('field_test_multiple', $view_display->getComponent("field_{$field_name}")['type']);
+    $this->assertEquals('altered dummy test string', $view_display->getComponent("field_{$field_name}")['settings']['test_formatter_setting_multiple']);
+  }
+
+  /**
+   * Tests that options are copied over when reusing a field.
+   *
+   * @dataProvider entityTypesProvider
+   */
+  public function testReuseFieldMultipleDisplay($entity_type, $bundle1, $bundle2) {
+    // Create additional form mode and enable it on both bundles.
+    EntityFormMode::create([
+      'id' => "{$entity_type}.little",
+      'label' => 'Little Form',
+      'targetEntityType' => $entity_type,
+    ])->save();
+    $form_display = EntityFormDisplay::create([
+      'id' => "{$entity_type}.{$bundle1['id']}.little",
+      'targetEntityType' => $entity_type,
+      'status' => TRUE,
+      'bundle' => $bundle1['id'],
+      'mode' => 'little',
+    ]);
+    $form_display->save();
+    EntityFormDisplay::create([
+      'id' => "{$entity_type}.{$bundle2['id']}.little",
+      'targetEntityType' => $entity_type,
+      'status' => TRUE,
+      'bundle' => $bundle2['id'],
+      'mode' => 'little',
+    ])->save();
+
+    // Create additional view mode and enable it on both bundles.
+    EntityViewMode::create([
+      'id' => "{$entity_type}.little",
+      'targetEntityType' => $entity_type,
+      'status' => TRUE,
+      'enabled' => TRUE,
+      'label' => 'Little View Mode',
+    ])->save();
+    $view_display = EntityViewDisplay::create([
+      'id' => "{$entity_type}.{$bundle1['id']}.little",
+      'targetEntityType' => $entity_type,
+      'status' => TRUE,
+      'bundle' => $bundle1['id'],
+      'mode' => 'little',
+    ]);
+    $view_display->save();
+    EntityViewDisplay::create([
+      'id' => "{$entity_type}.{$bundle2['id']}.little",
+      'targetEntityType' => $entity_type,
+      'status' => TRUE,
+      'bundle' => $bundle2['id'],
+      'mode' => 'little',
+    ])->save();
+
+    $field_name = 'test_reuse';
+    $label = $this->randomMachineName();
+
+    // Create field with pre-configured options.
+    $this->drupalGet($bundle1['path'] . "/fields/add-field");
+    $this->fieldUIAddNewField(NULL, $field_name, $label, 'field_ui:test_field_with_preconfigured_options:custom_options');
+    $view_display->setComponent("field_{$field_name}", [
+      'type' => 'field_test_default',
+      'region' => 'content',
+    ])->save();
+    $form_display->setComponent("field_{$field_name}", [
+      'type' => 'test_field_widget',
+      'region' => 'content',
+    ])->save();
+
+    $new_label = $this->randomMachineName();
+    $this->fieldUIAddExistingField($bundle2['path'], "field_{$field_name}", $new_label);
+
+    $field = FieldConfig::loadByName($entity_type, $bundle2['id'], "field_{$field_name}");
+    $this->assertTrue($field->isRequired());
+    $this->assertEquals($new_label, $field->label());
+    $this->assertEquals('preconfigured_field_setting', $field->getSetting('test_field_setting'));
+
+    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
+    $display_repository = \Drupal::service('entity_display.repository');
+
+    // Ensure that the additional form display has correct settings.
+    $form_display = $display_repository->getFormDisplay($entity_type, $bundle2['id'], $form_display->getMode());
+    $this->assertEquals('test_field_widget', $form_display->getComponent("field_{$field_name}")['type']);
+
+    // Ensure that the additional view display has correct settings.
+    $view_display = $display_repository->getViewDisplay($entity_type, $bundle2['id'], $view_display->getMode());
+    $this->assertEquals('field_test_default', $view_display->getComponent("field_{$field_name}")['type']);
+  }
+
+  /**
+   * Data provider for testing Field UI with multiple entity types.
+   *
+   * @return array
+   *   Test cases.
+   */
+  public function entityTypesProvider() {
+    return [
+      'node' => [
+        'entity_type' => 'node',
+        'article' => [
+          'id' => 'article',
+          'path' => 'admin/structure/types/manage/article',
+        ],
+        'page' => [
+          'id' => 'page',
+          'path' => 'admin/structure/types/manage/page',
+        ],
+      ],
+      'taxonomy' => [
+        'entity_type' => 'taxonomy_term',
+        'tags' => [
+          'id' => 'tags',
+          'path' => 'admin/structure/taxonomy/manage/tags/overview',
+        ],
+        'kittens' => [
+          'id' => 'kittens',
+          'path' => 'admin/structure/taxonomy/manage/kittens/overview',
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
index 7a97872b0ff3d59f4e842d953c5aab86bf905f93..9a5c6de5487dc224c5247b7f6ac986a34f413264 100644
--- a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
+++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
@@ -2,7 +2,11 @@
 
 namespace Drupal\Tests\field_ui\Functional;
 
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
+use Drupal\user\Entity\User;
 
 // cSpell:ignore downlander
 
@@ -10,13 +14,16 @@
  * Tests the Manage Display page of a fieldable entity type.
  *
  * @group field_ui
+ * @group #slow
  */
 class ManageFieldsTest extends BrowserTestBase {
 
+  use FieldUiTestTrait;
   /**
    * {@inheritdoc}
    */
   protected static $modules = [
+    'field_test',
     'field_ui',
     'field_ui_test',
     'node',
@@ -28,13 +35,20 @@ class ManageFieldsTest extends BrowserTestBase {
    */
   protected $defaultTheme = 'stark';
 
+  /**
+   * A user with permission to administer node fields, etc.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
   /**
    * {@inheritdoc}
    */
   protected function setUp(): void {
     parent::setUp();
-    $account = $this->drupalCreateUser(['administer node fields']);
-    $this->drupalLogin($account);
+    $this->adminUser = $this->drupalCreateUser(['administer node fields']);
+    $this->drupalLogin($this->adminUser);
     $this->config('system.logging')
       ->set('error_level', ERROR_REPORTING_DISPLAY_ALL)
       ->save();
@@ -112,4 +126,182 @@ public function testFieldDropButtonOperations() {
     $this->assertStringContainsString($allowed_bundles_text, $element->getText());
   }
 
+  /**
+   * Tests adding a field.
+   */
+  public function testAddField() {
+    $page = $this->getSession()->getPage();
+    $type = $this->drupalCreateContentType([
+      'name' => 'Article',
+      'type' => 'article',
+    ]);
+
+    // Create a new field without actually saving it.
+    $this->fieldUIAddNewField('admin/structure/types/manage/' . $type->id(), 'test_field', 'Test field', 'test_field', [], [], FALSE);
+    // Assert that the field was not created.
+    $this->assertNull(FieldStorageConfig::loadByName('node', "field_test_field"));
+
+    $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
+    $edit = [
+      'label' => 'Test field',
+      'field_name' => 'test_field',
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
+    $this->assertSession()->statusMessageNotContains('Saved');
+
+    // Change the storage form values.
+    $edit = ['cardinality_number' => 5];
+    $this->submitForm($edit, 'Continue');
+    $this->assertSession()->statusMessageNotContains('Saved');
+
+    // Go back to the field storage form.
+    $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/add-storage/node/field_test_field');
+    // Assert that the form values persist.
+    $this->assertEquals(5, $page->findField('cardinality_number')->getValue());
+
+    // Try creating a field with the same machine name.
+    $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
+    $edit = [
+      'label' => 'Test field',
+      'field_name' => 'test_field',
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
+    // Assert that the values in the field storage form are reset.
+    $this->assertEquals(1, $page->findField('cardinality_number')->getValue());
+
+    // Assert that the field is created with the new settings.
+    $this->submitForm([], 'Continue');
+    $this->assertSession()->statusMessageNotContains('Saved');
+    $this->submitForm([], 'Save settings');
+    $this->assertSession()->statusMessageContains('Saved');
+
+    $this->assertEquals(1, FieldStorageConfig::loadByName('node', 'field_test_field')->getCardinality());
+  }
+
+  /**
+   * Tests multiple users adding a field with the same name.
+   */
+  public function testAddFieldWithMultipleUsers() {
+    $page = $this->getSession()->getPage();
+    // Create two users.
+    $user1 = $this->drupalCreateUser(['administer node fields']);
+    $user2 = $this->drupalCreateUser(['administer node fields']);
+
+    $node_type = $this->drupalCreateContentType();
+    $bundle_path = '/admin/structure/types/manage/' . $node_type->id();
+
+    // Start adding a field as user 1, stop prior to saving, but keep the URL.
+    $this->drupalLogin($user1);
+    $this->drupalGet($bundle_path . '/fields/add-field');
+    $edit = [
+      'label' => 'Test field',
+      'field_name' => 'test_field',
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
+    // Make changes to the storage form.
+    $edit = ['cardinality_number' => 5];
+    $storage_form_url = $this->getUrl();
+    $this->submitForm($edit, 'Continue');
+    $this->drupalLogout();
+
+    // Actually add a field as user 2.
+    $this->drupalLogin($user2);
+    $this->drupalGet($bundle_path . '/fields/add-field');
+    $edit = [
+      'label' => 'Test field',
+      'field_name' => 'test_field',
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
+    $allowed_no_of_values = $page->findField('cardinality_number')->getValue();
+    // Assert that the changes made by any user do not affect other users until
+    // the field is saved.
+    $this->assertEquals(1, $allowed_no_of_values);
+    $this->submitForm(['cardinality_number' => 2], 'Continue');
+    $this->submitForm([], 'Save settings');
+    $this->assertSession()->pageTextContains("Saved Test field configuration.");
+    $this->drupalLogout();
+
+    // Continue adding a field as user 1, using the URL saved previously.
+    $this->drupalLogin($user1);
+    $this->drupalGet($storage_form_url);
+    $this->submitForm([], 'Continue');
+    // Assert that the user can go on with configuring a field with a machine
+    // that is already taken.
+    $this->assertSession()->pageTextNotContains('error');
+    $this->submitForm([], 'Save settings');
+    // An error is thrown only after the final 'Save'.
+    $this->assertSession()->statusMessageContains("An error occurred while saving the field: 'field_storage_config' entity with ID 'node.field_test_field' already exists.");
+  }
+
+  /**
+   * Tests editing field when the field exists in temp store.
+   */
+  public function testEditFieldWithLeftOverFieldInTempStore() {
+    $user = $this->drupalCreateUser(['administer node fields']);
+
+    $node_type = $this->drupalCreateContentType();
+    $bundle_path = '/admin/structure/types/manage/' . $node_type->id();
+
+    // Start adding a field but stop prior to saving.
+    $this->drupalLogin($user);
+    $this->drupalGet($bundle_path . '/fields/add-field');
+    $edit = [
+      'label' => 'Test field',
+      'field_name' => 'test_field',
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
+
+    /** @var \Drupal\field\FieldStorageConfigInterface $storage */
+    $storage = $this->container->get('entity_type.manager')
+      ->getStorage('field_storage_config')
+      ->create([
+        'type' => 'test_field',
+        'field_name' => 'test_field',
+        'entity_type' => 'node',
+      ]);
+    $storage->save();
+
+    $this->container->get('entity_type.manager')
+      ->getStorage('field_config')
+      ->create([
+        'field_storage' => $storage,
+        'bundle' => $node_type->id(),
+        'entity_type' => 'node',
+      ])
+      ->save();
+
+    $this->drupalGet("$bundle_path/fields/node.{$node_type->id()}.test_field/storage");
+    $this->submitForm([], 'Save');
+    $this->assertSession()->statusMessageContains('Your settings have been saved.', 'status');
+
+    $this->drupalGet("$bundle_path/fields/node.{$node_type->id()}.test_field");
+    $this->submitForm([], 'Save settings');
+    $this->assertSession()->statusMessageContains('Saved test_field configuration.', 'status');
+  }
+
+  /**
+   * Tests creating entity reference field to non-bundleable entity type.
+   */
+  public function testEntityReferenceToNonBundleableEntity() {
+    $type = $this->drupalCreateContentType([
+      'name' => 'kittens',
+      'type' => 'kittens',
+    ]);
+    $bundle_path = 'admin/structure/types/manage/' . $type->id();
+    $field_name = 'field_user_reference';
+
+    $field_edit = [
+      'set_default_value' => '1',
+      "default_value_input[$field_name][0][target_id]" => $this->adminUser->label() . ' (' . $this->adminUser->id() . ')',
+    ];
+    $this->fieldUIAddNewField($bundle_path, 'user_reference', NULL, 'field_ui:entity_reference:user', [], $field_edit);
+    $field = FieldConfig::loadByName('node', 'kittens', $field_name);
+    $this->assertEquals([['target_id' => $this->adminUser->id()]], $field->getDefaultValue(User::create(['name' => '1337'])));
+  }
+
 }
diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php
index ab3d845c66a17cbbb0176d0884e26788e188aeee..3db32a07236dae766271f13a03c91bcde86c6c32 100644
--- a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php
+++ b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php
@@ -180,40 +180,43 @@ public function testAddField() {
     $page->fillField('label', $field_name);
 
     // Test validation.
-    $page->pressButton('Save and continue');
+    $page->pressButton('Continue');
     $assert_session->pageTextContains('You need to select a field type.');
     $assert_session->elementExists('css', '[name="new_storage_type"].error');
     $assert_session->pageTextNotContains('Choose an option below');
 
-    $this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]'));
+    $this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]')->getParent());
     $number_field->click();
     $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="number"]')->isSelected());
     $assert_session->pageTextContains('Choose an option below');
-    $page->pressButton('Save and continue');
+    $page->pressButton('Continue');
     $assert_session->pageTextContains('You need to select a field type.');
     $assert_session->elementNotExists('css', '[name="new_storage_type"].error');
     $assert_session->elementExists('css', '[name="group_field_options_wrapper"].error');
 
     // Try adding a field using a grouped field type.
-    $this->assertNotEmpty($email_field = $page->find('xpath', '//*[text() = "Email"]'));
+    $this->assertNotEmpty($email_field = $page->find('xpath', '//*[text() = "Email"]')->getParent());
     $email_field->click();
     $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="email"]')->isSelected());
     $assert_session->pageTextNotContains('Choose an option below');
 
-    $this->assertNotEmpty($text = $page->find('xpath', '//*[text() = "Plain text"]'));
+    $this->assertNotEmpty($text = $page->find('xpath', '//*[text() = "Plain text"]')->getParent());
     $text->click();
     $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="plain_text"]')->isSelected());
     $assert_session->pageTextContains('Choose an option below');
 
-    $this->assertNotEmpty($text_plain = $page->find('xpath', '//*[text() = "Text (plain)"]'));
+    $this->assertNotEmpty($text_plain = $page->find('xpath', '//*[text() = "Text (plain)"]')->getParent());
     $text_plain->click();
     $this->assertTrue($assert_session->elementExists('css', '[name="group_field_options_wrapper"][value="string"]')->isSelected());
-
-    $page->pressButton('Save and continue');
-    $assert_session->pageTextContains('Your settings have been saved.');
+    $page->pressButton('Continue');
+    $this->assertMatchesRegularExpression('/.*article\/add-storage\/node\/field_test_field_1.*/', $this->getUrl());
+    $page->pressButton('Continue');
+    $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_test_field_1.*/', $this->getUrl());
+    $page->pressButton('Save settings');
+    $assert_session->pageTextContains('Saved ' . $field_name . ' configuration.');
     $this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name"));
     $this->assertEquals('string', $field_storage->getType());
 
@@ -222,23 +225,27 @@ public function testAddField() {
     $field_name = 'test_field_2';
     $page->fillField('label', $field_name);
 
-    $this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]'));
+    $this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]')->getParent());
     $number_field->click();
     $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="number"]')->isSelected());
     $assert_session->pageTextContains('Choose an option below');
-    $this->assertNotEmpty($number_integer = $page->find('xpath', '//*[text() = "Number (integer)"]'));
+    $this->assertNotEmpty($number_integer = $page->find('xpath', '//*[text() = "Number (integer)"]')->getParent());
     $number_integer->click();
     $this->assertTrue($assert_session->elementExists('css', '[name="group_field_options_wrapper"][value="integer"]')->isSelected());
 
-    $this->assertNotEmpty($test_field = $page->find('xpath', '//*[text() = "Test field"]'));
+    $this->assertNotEmpty($test_field = $page->find('xpath', '//*[text() = "Test field"]')->getParent());
     $test_field->click();
     $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="test_field"]')->isSelected());
     $assert_session->pageTextNotContains('Choose an option below');
 
-    $page->pressButton('Save and continue');
-    $assert_session->pageTextContains('Your settings have been saved.');
+    $page->pressButton('Continue');
+    $this->assertMatchesRegularExpression('/.*article\/add-storage\/node\/field_test_field_2.*/', $this->getUrl());
+    $page->pressButton('Continue');
+    $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_test_field_2.*/', $this->getUrl());
+    $page->pressButton('Save settings');
+    $assert_session->pageTextContains('Saved ' . $field_name . ' configuration.');
     $this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name"));
     $this->assertEquals('test_field', $field_storage->getType());
   }
@@ -255,7 +262,7 @@ public function testFieldTypeOrder() {
     ];
     foreach ($field_type_categories as $field_type_category) {
       // Select the group card.
-      $group_field_card = $page->find('css', "[name='new_storage_type'][value='$field_type_category']");
+      $group_field_card = $page->find('css', "[name='new_storage_type'][value='$field_type_category']")->getParent();
       $group_field_card->click();
       $this->assertSession()->assertWaitOnAjaxRequest();
       $field_types = $page->findAll('css', '.subfield-option .option');
diff --git a/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php b/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php
index f1c57fc03b3705a5a7b2cdcc7eca20911491b04e..1a4625095d6affdc2b8a9cb3cf4877bfe082474f 100644
--- a/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php
+++ b/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php
@@ -43,7 +43,7 @@ public function fieldUIAddNewFieldJS(?string $bundle_path, string $field_name, ?
 
     if ($assert_session->waitForElementVisible('css', "[name='new_storage_type'][value='$field_type']")) {
       $page = $this->getSession()->getPage();
-      $field_card = $page->find('css', "[name='new_storage_type'][value='$field_type']");
+      $field_card = $page->find('css', "[name='new_storage_type'][value='$field_type']")->getParent();
     }
     else {
       $field_card = $this->getFieldFromGroupJS($field_type);
@@ -61,17 +61,11 @@ public function fieldUIAddNewFieldJS(?string $bundle_path, string $field_name, ?
     $this->assertTrue($field_field_name->isVisible());
     $field_field_name->setValue($field_name);
 
-    $page->findButton('Save and continue')->click();
+    $page->findButton('Continue')->click();
     $assert_session->waitForText("These settings apply to the $label field everywhere it is used.");
     if ($save_settings) {
-      $breadcrumb_link = $page->findLink($label);
-
-      // Test breadcrumb.
-      $this->assertTrue($breadcrumb_link->isVisible());
-
       // Second step: 'Storage settings' form.
-      $page->findButton('Save field settings')->click();
-      $assert_session->pageTextContains("Updated field $label field settings.");
+      $page->findButton('Continue')->click();
 
       // Third step: 'Field settings' form.
       $page->findButton('Save settings')->click();
@@ -145,7 +139,7 @@ public function getFieldFromGroupJS($field_type) {
     }
     $field_card = NULL;
     foreach ($groups as $group) {
-      $group_field_card = $this->getSession()->getPage()->find('css', "[name='new_storage_type'][value='$group']");
+      $group_field_card = $this->getSession()->getPage()->find('css', "[name='new_storage_type'][value='$group']")->getParent();
       $group_field_card->click();
       $this->assertSession()->assertWaitOnAjaxRequest();
       $field_card = $this->getSession()->getPage()->find('css', "[name='group_field_options_wrapper'][value='$field_type']");
@@ -153,7 +147,7 @@ public function getFieldFromGroupJS($field_type) {
         break;
       }
     }
-    return $field_card;
+    return $field_card->getParent();
   }
 
 }
diff --git a/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php b/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php
index 3fca460a0cedebd2df3f709c30539f7d07e1c0ac..9a01349a30770de684ca8c83516e86652ca735dc 100644
--- a/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php
+++ b/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php
@@ -42,12 +42,12 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi
     // page before calling this method.
     if ($bundle_path !== NULL) {
       $bundle_path = "$bundle_path/fields/add-field";
-    }
-
-    // First step: 'Add field' page.
-    if ($bundle_path !== NULL) {
+      // First step: 'Add field' page.
       $this->drupalGet($bundle_path);
     }
+    else {
+      $bundle_path = $this->getUrl();
+    }
 
     try {
       // First check if the passed in field type is not part of a group.
@@ -76,27 +76,25 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi
         ];
       }
     }
-    $this->submitForm($initial_edit, 'Save and continue');
+    $this->submitForm($initial_edit, 'Continue');
+    // Assert that the field is not created.
+    $this->assertFieldDoesNotExist($bundle_path, $label);
     if ($save_settings) {
       $this->assertSession()->pageTextContains("These settings apply to the $label field everywhere it is used.");
       // Test Breadcrumbs.
       $this->getSession()->getPage()->findLink($label);
 
       // Second step: 'Storage settings' form.
-      $this->submitForm($storage_edit, 'Save field settings');
-      $this->assertSession()
-        ->pageTextContains("Updated field $label field settings.");
+      $this->submitForm($storage_edit, 'Continue');
+      // Assert that the field is not created.
+      $this->assertFieldDoesNotExist($bundle_path, $label);
 
       // Third step: 'Field settings' form.
       $this->submitForm($field_edit, 'Save settings');
       $this->assertSession()->pageTextContains("Saved $label configuration.");
 
       // Check that the field appears in the overview form.
-      $xpath = $this->assertSession()
-        ->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
-          ':label' => $label,
-        ]);
-      $this->assertSession()->elementExists('xpath', $xpath);
+      $this->assertFieldExistsOnOverview($label);
     }
   }
 
@@ -205,4 +203,53 @@ public function getFieldFromGroup($field_type) {
     return NULL;
   }
 
+  /**
+   * Asserts that the field doesn't exist in the overview form.
+   *
+   * @param string $bundle_path
+   *   The bundle path.
+   * @param string $label
+   *   The field label.
+   */
+  protected function assertFieldDoesNotExist(string $bundle_path, string $label) {
+    $original_url = $this->getUrl();
+    $this->drupalGet(explode('/fields', $bundle_path)[0] . '/fields');
+    $this->assertFieldDoesNotExistOnOverview($label);
+    $this->drupalGet($original_url);
+  }
+
+  /**
+   * Asserts that the field appears on the overview form.
+   *
+   * @param string $label
+   *   The field label.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   */
+  protected function assertFieldExistsOnOverview(string $label) {
+    $xpath = $this->assertSession()
+      ->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
+        ':label' => $label,
+      ]);
+    $element = $this->getSession()->getPage()->find('xpath', $xpath);
+    if ($element === NULL) {
+      throw new ElementNotFoundException($this->getSession()->getDriver(), 'form field', 'label', $label);
+    }
+  }
+
+  /**
+   * Asserts that the field does not appear on the overview form.
+   *
+   * @param string $label
+   *   The field label.
+   */
+  protected function assertFieldDoesNotExistOnOverview(string $label) {
+    $xpath = $this->assertSession()
+      ->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
+        ':label' => $label,
+      ]);
+    $element = $this->getSession()->getPage()->find('xpath', $xpath);
+    $this->assertSession()->assert($element === NULL, sprintf('A field "%s" appears on this page, but it should not.', $label));
+  }
+
 }
diff --git a/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php b/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php
index 00267079bcb30aaa8b49fbbd7d7c901f6131bdbc..650baff4c69395365865d9f1f81241179a18c086 100644
--- a/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php
+++ b/core/modules/field_ui/tests/src/Unit/FieldConfigEditFormTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\field_ui\Unit;
 
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+use Drupal\Core\TempStore\PrivateTempStore;
 use Drupal\field_ui\Form\FieldConfigEditForm;
 use Drupal\Tests\UnitTestCase;
 
@@ -27,7 +29,9 @@ protected function setUp(): void {
 
     $entity_type_bundle_info = $this->createMock('\Drupal\Core\Entity\EntityTypeBundleInfoInterface');
     $typed_data = $this->createMock('\Drupal\Core\TypedData\TypedDataManagerInterface');
-    $this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info, $typed_data);
+    $temp_store = $this->createMock(PrivateTempStore::class);
+    $entity_display_repository = $this->createMock(EntityDisplayRepositoryInterface::class);
+    $this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info, $typed_data, $entity_display_repository, $temp_store);
   }
 
   /**
diff --git a/core/modules/file/config/install/file.settings.yml b/core/modules/file/config/install/file.settings.yml
index 9191a0b8004e61f6174aa7f935f79d7007470530..a3809aaa9d2f99b52b9c4d8299057009e1f27449 100644
--- a/core/modules/file/config/install/file.settings.yml
+++ b/core/modules/file/config/install/file.settings.yml
@@ -4,3 +4,10 @@ description:
 icon:
   directory: 'core/modules/file/icons'
 make_unused_managed_files_temporary: false
+filename_sanitization:
+  transliterate: false
+  replace_whitespace: false
+  replace_non_alphanumeric: false
+  deduplicate_separators: false
+  lowercase: false
+  replacement_character: '-'
diff --git a/core/modules/file/config/schema/file.schema.yml b/core/modules/file/config/schema/file.schema.yml
index f94fbec950f14c28d23bd71a84058f985ae759aa..aaa74af750c4fa9a7a4abc49d3b615d585fd8637 100644
--- a/core/modules/file/config/schema/file.schema.yml
+++ b/core/modules/file/config/schema/file.schema.yml
@@ -24,6 +24,28 @@ file.settings:
     make_unused_managed_files_temporary:
       type: boolean
       label: 'Controls if unused files should be marked temporary'
+    filename_sanitization:
+      type: mapping
+      label: 'Uploaded filename sanitization options'
+      mapping:
+        transliterate:
+          type: boolean
+          label: 'Transliterate'
+        replace_whitespace:
+          type: boolean
+          label: 'Replace whitespace'
+        replace_non_alphanumeric:
+          type: boolean
+          label: 'Replace non-alphanumeric characters except dot, underscore and dash'
+        deduplicate_separators:
+          type: boolean
+          label: 'Replace sequences of dots, underscores and/or dashes with the replacement character'
+        lowercase:
+          type: boolean
+          label: 'Convert to lowercase'
+        replacement_character:
+          type: string
+          label: 'Character to use in replacements'
 
 field.storage_settings.file:
   type: base_entity_reference_field_settings
diff --git a/core/modules/file/css/file.icon.theme.css b/core/modules/file/css/file.icon.theme.css
index 0853ae51d116ac5f12e7e4d308849eb9fc95f00d..cde89c79dce5bd105261e0d081c8ae72817c2a11 100644
--- a/core/modules/file/css/file.icon.theme.css
+++ b/core/modules/file/css/file.icon.theme.css
@@ -5,5 +5,5 @@
  * @preserve
  */
 .field-icon-file_upload {
-  background-image: url("data:image/svg+xml,%3csvg width='18' height='20' fill='none' xmlns='http://www.w3.org/2000/svg'%3e  %3cpath d='M12 2H2v16h14V6h-4V2ZM0 .992C0 .444.447 0 .999 0H13l5 5v13.992A1 1 0 0 1 17.007 20H.993A1 1 0 0 1 0 19.008V.992Z' fill='%2355565B'/%3e  %3cpath d='M10.25 13v3h-2.5v-3H5l4-5 4 5h-2.75Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m3.87 1.163c-.361.17-.581.394-.745.757-.122.269-.122.303-.123 16.08l-.001 15.81.141.303c.166.355.54.699.87.797.173.052 3.612.07 13.532.07 14.778 0 13.513.037 13.978-.408.128-.122.282-.33.344-.462.107-.232.111-.689.113-12.93l.001-12.69-3.735-3.735-3.735-3.735-10.17.001h-10.17zm19.11 5.857v3h6v21.96h-22.98v-27.96h16.98zm-9.215 11.981-3.703 3.949 1.959.016 1.959.016v5.998h7.02v-5.998l1.969-.016 1.968-.016-3.684-3.93c-2.027-2.162-3.707-3.938-3.734-3.949-.028-.01-1.717 1.759-3.754 3.93' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/file/file.field_type_categories.yml b/core/modules/file/file.field_type_categories.yml
index efb40e7eeb03dce1c233cabd6ae56a51137aecd3..8f96134e8f4edf35396504ed58dd66e5c762a57e 100644
--- a/core/modules/file/file.field_type_categories.yml
+++ b/core/modules/file/file.field_type_categories.yml
@@ -2,3 +2,5 @@ file_upload:
   label: 'File upload'
   description: 'Field to upload any type of files.'
   weight: -15
+  libraries:
+    - file/drupal.file-icon
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 220ec88bc47e2d63e6b67156e28216091a9ddd90..b07fe158eec2a22344e239f574472cd5f6b594e1 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -22,6 +22,7 @@
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Url;
 use Drupal\file\Entity\File;
@@ -199,12 +200,18 @@ function file_validate_size(FileInterface $file, $file_limit = 0, $user_limit =
   $errors = [];
 
   if ($file_limit && $file->getSize() > $file_limit) {
-    $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', ['%filesize' => format_size($file->getSize()), '%maxsize' => format_size($file_limit)]);
+    $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', [
+      '%filesize' => ByteSizeMarkup::create($file->getSize()),
+      '%maxsize' => ByteSizeMarkup::create($file_limit),
+    ]);
   }
 
   // Save a query by only calling spaceUsed() when a limit is provided.
   if ($user_limit && (\Drupal::entityTypeManager()->getStorage('file')->spaceUsed($user->id()) + $file->getSize()) > $user_limit) {
-    $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', ['%filesize' => format_size($file->getSize()), '%quota' => format_size($user_limit)]);
+    $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', [
+      '%filesize' => ByteSizeMarkup::create($file->getSize()),
+      '%quota' => ByteSizeMarkup::create($user_limit),
+    ]);
   }
 
   return $errors;
@@ -673,7 +680,7 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL
     catch (IniSizeFileException | FormSizeFileException $e) {
       \Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', [
         '%file' => $uploaded_file->getFilename(),
-        '%maxsize' => format_size(Environment::getUploadMaxSize()),
+        '%maxsize' => ByteSizeMarkup::create(Environment::getUploadMaxSize()),
       ]));
       $files[$i] = FALSE;
     }
@@ -790,7 +797,7 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta
           break;
 
         case 'size':
-          $replacements[$original] = format_size($file->getSize());
+          $replacements[$original] = ByteSizeMarkup::create($file->getSize());
           break;
 
         case 'url':
@@ -1080,7 +1087,7 @@ function template_preprocess_file_link(&$variables) {
   // Set file classes to the options array.
   $variables['attributes'] = new Attribute($variables['attributes']);
   $variables['attributes']->addClass($classes);
-  $variables['file_size'] = $file->getSize() !== NULL ? format_size($file->getSize()) : '';
+  $variables['file_size'] = $file->getSize() !== NULL ? ByteSizeMarkup::create($file->getSize()) : '';
 
   $variables['link'] = Link::fromTextAndUrl($link_text, $url->mergeOptions($options))->toRenderable();
 }
@@ -1230,10 +1237,10 @@ function template_preprocess_file_upload_help(&$variables) {
   }
   if (isset($upload_validators['file_validate_size'])) {
     @trigger_error('\'file_validate_size\' is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use the \'FileSizeLimit\' constraint instead. See https://www.drupal.org/node/3363700', E_USER_DEPRECATED);
-    $descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['file_validate_size'][0])]);
+    $descriptions[] = t('@size limit.', ['@size' => ByteSizeMarkup::create($upload_validators['file_validate_size'][0])]);
   }
   if (isset($upload_validators['FileSizeLimit'])) {
-    $descriptions[] = t('@size limit.', ['@size' => format_size($upload_validators['FileSizeLimit']['fileLimit'])]);
+    $descriptions[] = t('@size limit.', ['@size' => ByteSizeMarkup::create($upload_validators['FileSizeLimit']['fileLimit'])]);
   }
 
   if (isset($upload_validators['file_validate_extensions'])) {
@@ -1554,8 +1561,78 @@ function file_field_find_file_reference_column(FieldDefinitionInterface $field)
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Injects the file sanitization options into /admin/config/media/file-system.
+ *
+ * These settings are enforced during upload by the FileEventSubscriber that
+ * listens to the FileUploadSanitizeNameEvent event.
+ *
+ * @see \Drupal\system\Form\FileSystemForm
+ * @see \Drupal\Core\File\Event\FileUploadSanitizeNameEvent
+ * @see \Drupal\file\EventSubscriber\FileEventSubscriber
+ */
+function file_form_system_file_system_settings_alter(array &$form, FormStateInterface $form_state) {
+  $config = \Drupal::config('file.settings');
+  $form['filename_sanitization'] = [
+    '#type' => 'details',
+    '#title' => t('Sanitize filenames'),
+    '#description' => t('These settings only apply to new files as they are uploaded. Changes here do not affect existing file names.'),
+    '#open' => TRUE,
+    '#tree' => TRUE,
+  ];
+
+  $form['filename_sanitization']['replacement_character'] = [
+    '#type' => 'select',
+    '#title' => t('Replacement character'),
+    '#default_value' => $config->get('filename_sanitization.replacement_character'),
+    '#options' => [
+      '-' => t('Dash (-)'),
+      '_' => t('Underscore (_)'),
+    ],
+    '#description' => t('Used when replacing whitespace, replacing non-alphanumeric characters or transliterating unknown characters.'),
+  ];
+
+  $form['filename_sanitization']['transliterate'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Transliterate'),
+    '#default_value' => $config->get('filename_sanitization.transliterate'),
+    '#description' => t('Transliteration replaces any characters that are not alphanumeric, underscores, periods or hyphens with the replacement character. It ensures filenames only contain ASCII characters. It is recommended to keep transliteration enabled.'),
+  ];
+
+  $form['filename_sanitization']['replace_whitespace'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Replace whitespace with the replacement character'),
+    '#default_value' => $config->get('filename_sanitization.replace_whitespace'),
+  ];
+
+  $form['filename_sanitization']['replace_non_alphanumeric'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Replace non-alphanumeric characters with the replacement character'),
+    '#default_value' => $config->get('filename_sanitization.replace_non_alphanumeric'),
+    '#description' => t('Alphanumeric characters, dots <span aria-hidden="true">(.)</span>, underscores <span aria-hidden="true">(_)</span> and dashes <span aria-hidden="true">(-)</span> are preserved.'),
+  ];
+
+  $form['filename_sanitization']['deduplicate_separators'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Replace sequences of dots, underscores and/or dashes with the replacement character'),
+    '#default_value' => $config->get('filename_sanitization.deduplicate_separators'),
+  ];
+
+  $form['filename_sanitization']['lowercase'] = [
+    '#type' => 'checkbox',
+    '#title' => t('Convert to lowercase'),
+    '#default_value' => $config->get('filename_sanitization.lowercase'),
+  ];
+
+  $form['#submit'][] = 'file_system_settings_submit';
+}
+
+/**
+ * Form submission handler for file system settings form.
  */
-function file_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'file/drupal.file-icon';
+function file_system_settings_submit(array &$form, FormStateInterface $form_state) {
+  $config = \Drupal::configFactory()->getEditable('file.settings')
+    ->set('filename_sanitization', $form_state->getValue('filename_sanitization'));
+  $config->save();
 }
diff --git a/core/modules/file/file.post_update.php b/core/modules/file/file.post_update.php
index 22bbdd512ed42bf6ecde0cb839fc40fb1624045e..73c815b1cc0233c03d950cf278ff024527b06d79 100644
--- a/core/modules/file/file.post_update.php
+++ b/core/modules/file/file.post_update.php
@@ -29,3 +29,17 @@ function file_post_update_add_permissions_to_roles(?array &$sandbox = NULL): voi
     return TRUE;
   });
 }
+
+/**
+ * Add default filename sanitization configuration.
+ */
+function file_post_update_add_default_filename_sanitization_configuration() {
+  $config = \Drupal::configFactory()->getEditable('file.settings');
+  $config->set('filename_sanitization.transliterate', FALSE);
+  $config->set('filename_sanitization.replace_whitespace', FALSE);
+  $config->set('filename_sanitization.replace_non_alphanumeric', FALSE);
+  $config->set('filename_sanitization.deduplicate_separators', FALSE);
+  $config->set('filename_sanitization.lowercase', FALSE);
+  $config->set('filename_sanitization.replacement_character', '-');
+  $config->save();
+}
diff --git a/core/modules/file/file.services.yml b/core/modules/file/file.services.yml
index 64c612cfbbca3fae76b8b1a035e3a6e7a98e1073..370972c5ff0b754b8db32717c180b808258c6675 100644
--- a/core/modules/file/file.services.yml
+++ b/core/modules/file/file.services.yml
@@ -1,4 +1,9 @@
 services:
+  file.event.subscriber:
+    class: Drupal\file\EventSubscriber\FileEventSubscriber
+    arguments: ['@config.factory', '@transliteration', '@language_manager']
+    tags:
+      - { name: event_subscriber }
   file.usage:
     class: Drupal\file\FileUsage\DatabaseFileUsageBackend
     arguments: ['@config.factory', '@database', 'file_usage']
diff --git a/core/modules/file/src/Controller/FileWidgetAjaxController.php b/core/modules/file/src/Controller/FileWidgetAjaxController.php
index 992fcb174c23ae32b6b0b610b4600e65ac4906bf..098337887a940a24923288750b91bbcfaeea0345 100644
--- a/core/modules/file/src/Controller/FileWidgetAjaxController.php
+++ b/core/modules/file/src/Controller/FileWidgetAjaxController.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\file\Controller;
 
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Symfony\Component\HttpFoundation\JsonResponse;
 
@@ -30,9 +31,9 @@ public function progress($key) {
     if ($implementation == 'uploadprogress') {
       $status = uploadprogress_get_info($key);
       if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) {
-        $progress['message'] = $this->t('Uploading... (@current of @total)', [
-          '@current' => format_size($status['bytes_uploaded']),
-          '@total' => format_size($status['bytes_total']),
+        $progress['message'] = t('Uploading... (@current of @total)', [
+          '@current' => ByteSizeMarkup::create($status['bytes_uploaded']),
+          '@total' => ByteSizeMarkup::create($status['bytes_total']),
         ]);
         $progress['percentage'] = round(100 * $status['bytes_uploaded'] / $status['bytes_total']);
       }
diff --git a/core/modules/file/src/EventSubscriber/FileEventSubscriber.php b/core/modules/file/src/EventSubscriber/FileEventSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..17608c583fb48589fa5824c8c66da3fbe9fb9cb4
--- /dev/null
+++ b/core/modules/file/src/EventSubscriber/FileEventSubscriber.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\file\EventSubscriber;
+
+use Drupal\Component\Transliteration\TransliterationInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Sanitizes uploaded filenames.
+ *
+ * @package Drupal\file\EventSubscriber
+ */
+class FileEventSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Constructs a new file event listener.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   The config factory.
+   * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
+   *   The transliteration service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The language manager.
+   */
+  public function __construct(
+    protected ConfigFactoryInterface $configFactory,
+    protected TransliterationInterface $transliteration,
+    protected LanguageManagerInterface $languageManager,
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents(): array {
+    return [
+      FileUploadSanitizeNameEvent::class => 'sanitizeFilename',
+    ];
+  }
+
+  /**
+   * Sanitizes the filename of a file being uploaded.
+   *
+   * @param \Drupal\Core\File\Event\FileUploadSanitizeNameEvent $event
+   *   File upload sanitize name event.
+   *
+   * @see file_form_system_file_system_settings_alter()
+   */
+  public function sanitizeFilename(FileUploadSanitizeNameEvent $event) {
+    $fileSettings = $this->configFactory->get('file.settings');
+    $transliterate = $fileSettings->get('filename_sanitization.transliterate');
+
+    $filename = $event->getFilename();
+    $extension = pathinfo($filename, PATHINFO_EXTENSION);
+    if ($extension !== '') {
+      $extension = '.' . $extension;
+      $filename = pathinfo($filename, PATHINFO_FILENAME);
+    }
+
+    // Sanitize the filename according to configuration.
+    $alphanumeric = $fileSettings->get('filename_sanitization.replace_non_alphanumeric');
+    $replacement = $fileSettings->get('filename_sanitization.replacement_character');
+    if ($transliterate) {
+      $transliterated_filename = $this->transliteration->transliterate(
+        $filename,
+        $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(),
+        $replacement
+      );
+      if (mb_strlen($transliterated_filename) > 0) {
+        $filename = $transliterated_filename;
+      }
+      else {
+        // If transliteration has resulted in a zero length string enable the
+        // 'replace_non_alphanumeric' option and ignore the result of
+        // transliteration.
+        $alphanumeric = TRUE;
+      }
+    }
+    if ($fileSettings->get('filename_sanitization.replace_whitespace')) {
+      $filename = preg_replace('/\s/u', $replacement, trim($filename));
+    }
+    // Only honor replace_non_alphanumeric if transliterate is enabled.
+    if ($transliterate && $alphanumeric) {
+      $filename = preg_replace('/[^0-9A-Za-z_.-]/u', $replacement, $filename);
+    }
+    if ($fileSettings->get('filename_sanitization.deduplicate_separators')) {
+      $filename = preg_replace('/(_)_+|(\.)\.+|(-)-+/u', $replacement, $filename);
+      // Replace multiple separators with single one.
+      $filename = preg_replace('/(_|\.|\-)[(_|\.|\-)]+/u', $replacement, $filename);
+      $filename = preg_replace('/' . preg_quote($replacement) . '[' . preg_quote($replacement) . ']*/u', $replacement, $filename);
+      // Remove replacement character from the end of the filename.
+      $filename = rtrim($filename, $replacement);
+
+      // If there is an extension remove dots from the end of the filename to
+      // prevent duplicate dots.
+      if (!empty($extension)) {
+        $filename = rtrim($filename, '.');
+      }
+    }
+    if ($fileSettings->get('filename_sanitization.lowercase')) {
+      // Force lowercase to prevent issues on case-insensitive file systems.
+      $filename = mb_strtolower($filename);
+    }
+    $event->setFilename($filename . $extension);
+  }
+
+}
diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php b/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php
index 9bb080770a0273b9f936f2643123ca01756f3b74..b0a9d3c3414d6de44126d3c9b51152463629b69a 100644
--- a/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php
+++ b/core/modules/file/src/Plugin/Field/FieldFormatter/FileSize.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\FormatterBase;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 
 /**
  * Formatter that shows the file size in a human readable way.
@@ -33,7 +34,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
     $elements = [];
 
     foreach ($items as $delta => $item) {
-      $elements[$delta] = ['#markup' => format_size($item->value)];
+      $elements[$delta] = ['#markup' => ByteSizeMarkup::create((int) $item->value)];
     }
 
     return $elements;
diff --git a/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php b/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php
index e94156a6d36526b0680a3f7848f2453394fabf0d..b41a968b2eb4fefcf6e7da4f4e74624d1ac6f3ac 100644
--- a/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php
+++ b/core/modules/file/src/Plugin/Field/FieldFormatter/TableFormatter.php
@@ -3,6 +3,7 @@
 namespace Drupal\file\Plugin\Field\FieldFormatter;
 
 use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 
 /**
  * Plugin implementation of the 'file_table' formatter.
@@ -39,7 +40,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
               ],
             ],
           ],
-          ['data' => $file->getSize() !== NULL ? format_size($file->getSize()) : $this->t('Unknown')],
+          ['data' => $file->getSize() !== NULL ? ByteSizeMarkup::create($file->getSize()) : $this->t('Unknown')],
         ];
       }
 
diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
index 252bdf8bae424f9146b1c6cd68c5b035f33b46b7..b5947c9e5b7098163e6e0bade049e7706bf82835 100644
--- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
+++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php
@@ -2,8 +2,8 @@
 
 namespace Drupal\file\Plugin\Field\FieldType;
 
-use Drupal\Component\Utility\Bytes;
 use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Component\Utility\Bytes;
 use Drupal\Component\Utility\Environment;
 use Drupal\Component\Utility\Random;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -12,6 +12,7 @@
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\TypedData\DataDefinition;
 
@@ -191,7 +192,9 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
       '#type' => 'textfield',
       '#title' => $this->t('Maximum upload size'),
       '#default_value' => $settings['max_filesize'],
-      '#description' => $this->t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes could be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', ['%limit' => format_size(Environment::getUploadMaxSize())]),
+      '#description' => $this->t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes could be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', [
+        '%limit' => ByteSizeMarkup::create(Environment::getUploadMaxSize()),
+      ]),
       '#size' => 10,
       '#element_validate' => [[static::class, 'validateMaxFilesize']],
       '#weight' => 5,
diff --git a/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php b/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php
index 8bd0a1ad3719bd98284540a6b02e366333277b6e..d3886c1cff423f73735921f67bede7f1ed4c75ca 100644
--- a/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php
+++ b/core/modules/file/src/Plugin/Validation/Constraint/FileSizeLimitConstraintValidator.php
@@ -5,6 +5,7 @@
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@@ -50,8 +51,8 @@ public function validate(mixed $value, Constraint $constraint): void {
 
     if ($fileLimit && $file->getSize() > $fileLimit) {
       $this->context->addViolation($constraint->maxFileSizeMessage, [
-        '%filesize' => format_size($file->getSize()),
-        '%maxsize' => format_size($fileLimit),
+        '%filesize' => ByteSizeMarkup::create($file->getSize()),
+        '%maxsize' => ByteSizeMarkup::create($fileLimit),
       ]);
     }
 
@@ -64,8 +65,8 @@ public function validate(mixed $value, Constraint $constraint): void {
       $spaceUsed = $fileStorage->spaceUsed($this->currentUser->id()) + $file->getSize();
       if ($spaceUsed > $userLimit) {
         $this->context->addViolation($constraint->diskQuotaMessage, [
-          '%filesize' => format_size($file->getSize()),
-          '%quota' => format_size($userLimit),
+          '%filesize' => ByteSizeMarkup::create($file->getSize()),
+          '%quota' => ByteSizeMarkup::create($userLimit),
         ]);
       }
     }
diff --git a/core/modules/file/tests/src/Functional/FileFieldValidateTest.php b/core/modules/file/tests/src/Functional/FileFieldValidateTest.php
index 4cc0aaf885398667ae84fbc371e8fd140a27b19f..18967d329529c1e8fc093634fc0b391d7998c8f6 100644
--- a/core/modules/file/tests/src/Functional/FileFieldValidateTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldValidateTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\file\Entity\File;
 
@@ -103,12 +104,15 @@ public function testFileMaxSize() {
       $node = $node_storage->load($nid);
       $node_file = File::load($node->{$field_name}->target_id);
       $this->assertFileExists($node_file->getFileUri());
-      $this->assertFileEntryExists($node_file, new FormattableMarkup('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', ['%filesize' => format_size($small_file->getSize()), '%maxsize' => $max_filesize]));
+      $this->assertFileEntryExists($node_file, new FormattableMarkup('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', [
+        '%filesize' => ByteSizeMarkup::create($small_file->getSize()),
+        '%maxsize' => $max_filesize,
+      ]));
 
       // Check that uploading the large file fails (1M limit).
       $this->uploadNodeFile($large_file, $field_name, $type_name);
-      $filesize = format_size($large_file->getSize());
-      $maxsize = format_size($file_limit);
+      $filesize = ByteSizeMarkup::create($large_file->getSize());
+      $maxsize = ByteSizeMarkup::create($file_limit);
       $this->assertSession()->pageTextContains("The file is {$filesize} exceeding the maximum file size of {$maxsize}.");
     }
 
@@ -121,7 +125,9 @@ public function testFileMaxSize() {
     $node = $node_storage->load($nid);
     $node_file = File::load($node->{$field_name}->target_id);
     $this->assertFileExists($node_file->getFileUri());
-    $this->assertFileEntryExists($node_file, new FormattableMarkup('File entry exists after uploading a file (%filesize) with no max limit.', ['%filesize' => format_size($large_file->getSize())]));
+    $this->assertFileEntryExists($node_file, new FormattableMarkup('File entry exists after uploading a file (%filesize) with no max limit.', [
+      '%filesize' => ByteSizeMarkup::create($large_file->getSize()),
+    ]));
   }
 
   /**
diff --git a/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php b/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php
index 83e12945fd60a9e0097cbe693fb5805bf904981b..42511f153a3113a42b6247640a24d30e03052bdf 100644
--- a/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldWidgetTest.php
@@ -19,6 +19,7 @@
  * Tests the file field widget with public and private files.
  *
  * @group file
+ * @group #slow
  */
 class FileFieldWidgetTest extends FileFieldTestBase {
 
@@ -260,7 +261,7 @@ public function testPrivateFileSetting() {
     // Change the field setting to make its files private, and upload a file.
     $edit = ['settings[uri_scheme]' => 'private'];
     $this->drupalGet("admin/structure/types/manage/{$type_name}/fields/{$field_id}/storage");
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
     $node = $node_storage->loadUnchanged($nid);
     $node_file = File::load($node->{$field_name}->target_id);
diff --git a/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php b/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
index 67cb13cb2b672dda4db73d7ccc8924fbc8cd0f37..0319a1448862ba889b049bbe5d4abc1e8a61c664 100644
--- a/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
+++ b/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\file\Entity\File;
 
 /**
@@ -54,7 +55,7 @@ public function testFileTokenReplacement() {
     $tests['[file:name]'] = Html::escape($file->getFilename());
     $tests['[file:path]'] = Html::escape($file->getFileUri());
     $tests['[file:mime]'] = Html::escape($file->getMimeType());
-    $tests['[file:size]'] = format_size($file->getSize());
+    $tests['[file:size]'] = ByteSizeMarkup::create($file->getSize());
     $tests['[file:url]'] = Html::escape($file->createFileUrl(FALSE));
     $tests['[file:created]'] = $date_formatter->format($file->getCreatedTime(), 'medium', '', NULL, $language_interface->getId());
     $tests['[file:created:short]'] = $date_formatter->format($file->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
@@ -95,7 +96,7 @@ public function testFileTokenReplacement() {
     $tests['[file:name]'] = $file->getFilename();
     $tests['[file:path]'] = $file->getFileUri();
     $tests['[file:mime]'] = $file->getMimeType();
-    $tests['[file:size]'] = format_size($file->getSize());
+    $tests['[file:size]'] = ByteSizeMarkup::create($file->getSize());
 
     foreach ($tests as $input => $expected) {
       $output = $token_service->replace($input, ['file' => $file], ['langcode' => $language_interface->getId(), 'sanitize' => FALSE]);
diff --git a/core/modules/file/tests/src/Functional/FileUploadJsonBasicAuthTest.php b/core/modules/file/tests/src/Functional/FileUploadJsonBasicAuthTest.php
index 8a3e880921ec963a6d1d4ab5f6ba7c388d09a0a1..333f1ede7dd4d7f0bd076b8856e45cb145f9f711 100644
--- a/core/modules/file/tests/src/Functional/FileUploadJsonBasicAuthTest.php
+++ b/core/modules/file/tests/src/Functional/FileUploadJsonBasicAuthTest.php
@@ -7,6 +7,7 @@
 
 /**
  * @group file
+ * @group #slow
  */
 class FileUploadJsonBasicAuthTest extends FileUploadResourceTestBase {
 
diff --git a/core/modules/file/tests/src/Functional/FileUploadJsonCookieTest.php b/core/modules/file/tests/src/Functional/FileUploadJsonCookieTest.php
index 8b36780cd9d85de31a4de61ce2c59c1ce9de6907..733d517a0a834b43db90a7de95121ea8e823b286 100644
--- a/core/modules/file/tests/src/Functional/FileUploadJsonCookieTest.php
+++ b/core/modules/file/tests/src/Functional/FileUploadJsonCookieTest.php
@@ -7,6 +7,7 @@
 
 /**
  * @group file
+ * @group #slow
  */
 class FileUploadJsonCookieTest extends FileUploadResourceTestBase {
 
diff --git a/core/modules/file/tests/src/Functional/GenericTest.php b/core/modules/file/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a2e594d4663be99cc6522121d58b4d14fce4fca5
--- /dev/null
+++ b/core/modules/file/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\file\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for file.
+ *
+ * @group file
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/file/tests/src/Functional/RemoteFileSaveUploadTest.php b/core/modules/file/tests/src/Functional/RemoteFileSaveUploadTest.php
index bce058f1997ff95902539dd2c8aa81b1720caaea..64f33adf805db1c2e89f9c8fc20689c406ae36e8 100644
--- a/core/modules/file/tests/src/Functional/RemoteFileSaveUploadTest.php
+++ b/core/modules/file/tests/src/Functional/RemoteFileSaveUploadTest.php
@@ -6,6 +6,7 @@
  * Tests the file uploading functions.
  *
  * @group file
+ * @group #slow
  */
 class RemoteFileSaveUploadTest extends SaveUploadTest {
 
diff --git a/core/modules/file/tests/src/Functional/SaveUploadFormTest.php b/core/modules/file/tests/src/Functional/SaveUploadFormTest.php
index 7a7457446ecee8f101aaacb2f16aca1213560918..97b2988c6ceab1f45c9794421c81476deae466e6 100644
--- a/core/modules/file/tests/src/Functional/SaveUploadFormTest.php
+++ b/core/modules/file/tests/src/Functional/SaveUploadFormTest.php
@@ -10,6 +10,7 @@
  * Tests the _file_save_upload_from_form() function.
  *
  * @group file
+ * @group #slow
  *
  * @see _file_save_upload_from_form()
  */
diff --git a/core/modules/file/tests/src/Functional/SaveUploadTest.php b/core/modules/file/tests/src/Functional/SaveUploadTest.php
index d7ffe5a252e7ed062f25c0e46e1a71a738b076b5..e1dd3c5e99238de89844938d78bda2a4ea76672f 100644
--- a/core/modules/file/tests/src/Functional/SaveUploadTest.php
+++ b/core/modules/file/tests/src/Functional/SaveUploadTest.php
@@ -8,10 +8,13 @@
 use Drupal\file\Entity\File;
 use Drupal\Tests\TestFileCreationTrait;
 
+// cSpell:ignore TÉXT Pácê
+
 /**
  * Tests the file_save_upload() function.
  *
  * @group file
+ * @group #slow
  */
 class SaveUploadTest extends FileManagedTestBase {
 
@@ -59,13 +62,20 @@ class SaveUploadTest extends FileManagedTestBase {
    */
   protected $imageExtension;
 
+  /**
+   * The user used by the test.
+   *
+   * @var \Drupal\user\Entity\User
+   */
+  protected $account;
+
   /**
    * {@inheritdoc}
    */
   protected function setUp(): void {
     parent::setUp();
-    $account = $this->drupalCreateUser(['access site reports']);
-    $this->drupalLogin($account);
+    $this->account = $this->drupalCreateUser(['access site reports']);
+    $this->drupalLogin($this->account);
 
     $image_files = $this->drupalGetTestFiles('image');
     $this->image = File::create((array) current($image_files));
@@ -756,4 +766,135 @@ public function testRequired() {
     $this->assertSession()->responseContains('You WIN!');
   }
 
+  /**
+   * Tests filename sanitization.
+   */
+  public function testSanitization() {
+    $file = $this->generateFile('TÉXT-œ', 64, 5, 'text');
+
+    $this->drupalGet('file-test/upload');
+    // Upload a file with a name with uppercase and unicode characters.
+    $edit = [
+      'files[file_test_upload]' => \Drupal::service('file_system')->realpath($file),
+      'extensions' => 'txt',
+      'is_image_file' => FALSE,
+    ];
+    $this->submitForm($edit, 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+    // Test that the file name has not been sanitized.
+    $this->assertSession()->responseContains('File name is TÉXT-œ.txt.');
+
+    // Enable sanitization via the UI.
+    $admin = $this->createUser(['administer site configuration']);
+    $this->drupalLogin($admin);
+
+    // For now, just transliterate, with no other transformations.
+    $options = [
+      'filename_sanitization[transliterate]' => TRUE,
+      'filename_sanitization[replace_whitespace]' => FALSE,
+      'filename_sanitization[replace_non_alphanumeric]' => FALSE,
+      'filename_sanitization[deduplicate_separators]' => FALSE,
+      'filename_sanitization[lowercase]' => FALSE,
+      'filename_sanitization[replacement_character]' => '-',
+    ];
+    $this->drupalGet('admin/config/media/file-system');
+    $this->submitForm($options, 'Save configuration');
+
+    $this->drupalLogin($this->account);
+
+    // Upload a file with a name with uppercase and unicode characters.
+    $this->drupalGet('file-test/upload');
+    $this->submitForm($edit, 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+    // Test that the file name has been transliterated.
+    $this->assertSession()->responseContains('File name is TEXT-oe.txt.');
+    // Make sure we got a message about the rename.
+    $message = 'Your upload has been renamed to <em class="placeholder">TEXT-oe.txt</em>';
+    $this->assertSession()->responseContains($message);
+
+    // Generate another file with a name with All The Things(tm) we care about.
+    $file = $this->generateFile('S  Pácê--táb#	#--🙈', 64, 5, 'text');
+    $edit = [
+      'files[file_test_upload]' => \Drupal::service('file_system')->realpath($file),
+      'extensions' => 'txt',
+      'is_image_file' => FALSE,
+    ];
+    $this->submitForm($edit, 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+    // Test that the file name has only been transliterated.
+    $this->assertSession()->responseContains('File name is S  Pace--tab#	#---.txt.');
+
+    // Leave transliteration on and enable whitespace replacement.
+    $this->drupalLogin($admin);
+    $options['filename_sanitization[replace_whitespace]'] = TRUE;
+    $this->drupalGet('admin/config/media/file-system');
+    $this->submitForm($options, 'Save configuration');
+    $this->drupalLogin($this->account);
+
+    // Try again with the monster filename.
+    $this->drupalGet('file-test/upload');
+    $this->submitForm($edit, 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+    // Test that the file name has been transliterated and whitespace replaced.
+    $this->assertSession()->responseContains('File name is S--Pace--tab#-#---.txt.');
+
+    // Leave transliteration and whitespace replacement on, replace non-alpha.
+    $this->drupalLogin($admin);
+    $options['filename_sanitization[replace_non_alphanumeric]'] = TRUE;
+    $options['filename_sanitization[replacement_character]'] = '_';
+    $this->drupalGet('admin/config/media/file-system');
+    $this->submitForm($options, 'Save configuration');
+    $this->drupalLogin($this->account);
+
+    // Try again with the monster filename.
+    $this->drupalGet('file-test/upload');
+    $this->submitForm($edit, 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Test that the file name has been transliterated, whitespace replaced with
+    // '_', and non-alphanumeric characters replaced with '_'.
+    $this->assertSession()->responseContains('File name is S__Pace--tab___--_.txt.');
+
+    // Now turn on the setting to remove duplicate separators.
+    $this->drupalLogin($admin);
+    $options['filename_sanitization[deduplicate_separators]'] = TRUE;
+    $options['filename_sanitization[replacement_character]'] = '-';
+    $this->drupalGet('admin/config/media/file-system');
+    $this->submitForm($options, 'Save configuration');
+    $this->drupalLogin($this->account);
+
+    // Try again with the monster filename.
+    $this->drupalGet('file-test/upload');
+    $this->submitForm($edit, 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Test that the file name has been transliterated, whitespace replaced,
+    // non-alphanumeric characters replaced, and duplicate separators removed.
+    $this->assertSession()->responseContains('File name is S-Pace-tab.txt.');
+
+    // Finally, check the lowercase setting.
+    $this->drupalLogin($admin);
+    $options['filename_sanitization[lowercase]'] = TRUE;
+    $this->drupalGet('admin/config/media/file-system');
+    $this->submitForm($options, 'Save configuration');
+    $this->drupalLogin($this->account);
+
+    // Generate another file since we're going to start getting collisions with
+    // previously uploaded and renamed copies.
+    $file = $this->generateFile('S  Pácê--táb#	#--🙈-2', 64, 5, 'text');
+    $edit = [
+      'files[file_test_upload]' => \Drupal::service('file_system')->realpath($file),
+      'extensions' => 'txt',
+      'is_image_file' => FALSE,
+    ];
+    $this->drupalGet('file-test/upload');
+    $this->submitForm($edit, 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+    // Make sure all the sanitization options work as intended.
+    $this->assertSession()->responseContains('File name is s-pace-tab-2.txt.');
+    // Make sure we got a message about the rename.
+    $message = 'Your upload has been renamed to <em class="placeholder">s-pace-tab-2.txt</em>';
+    $this->assertSession()->responseContains($message);
+  }
+
 }
diff --git a/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php b/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
index c5f3362c8ecb0757a18ea90a56ad11ef27b50f6a..72c7fdb8f22215e5d2360fc37b93d6cdffc26dda 100644
--- a/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
+++ b/core/modules/file/tests/src/Kernel/FileUploadHandlerTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\file\Kernel;
 
 use Drupal\Component\Utility\Environment;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\file\Upload\UploadedFileInterface;
 use Drupal\KernelTests\KernelTestBase;
 use Symfony\Component\HttpFoundation\File\Exception\FormSizeFileException;
@@ -42,9 +43,9 @@ public function testFileSaveUploadSingleErrorFormSize() {
     $file_info = $this->createMock(UploadedFileInterface::class);
     $file_info->expects($this->once())->method('getError')->willReturn(UPLOAD_ERR_FORM_SIZE);
     $file_info->expects($this->once())->method('getClientOriginalName')->willReturn($file_name);
-    $file_info->expects($this->once())->method('getErrorMessage')->willReturn(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, format_size(Environment::getUploadMaxSize())));
+    $file_info->expects($this->once())->method('getErrorMessage')->willReturn(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, ByteSizeMarkup::create(Environment::getUploadMaxSize())));
     $this->expectException(FormSizeFileException::class);
-    $this->expectExceptionMessage(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, format_size(Environment::getUploadMaxSize())));
+    $this->expectExceptionMessage(sprintf('The file "%s" could not be saved because it exceeds %s, the maximum allowed size for uploads.', $file_name, ByteSizeMarkup::create(Environment::getUploadMaxSize())));
     $this->fileUploadHandler->handleFileUpload($file_info);
   }
 
diff --git a/core/modules/file/tests/src/Unit/SanitizeNameTest.php b/core/modules/file/tests/src/Unit/SanitizeNameTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..dfe3b4675c2162511bc617694726660555662bc9
--- /dev/null
+++ b/core/modules/file/tests/src/Unit/SanitizeNameTest.php
@@ -0,0 +1,247 @@
+<?php
+
+namespace Drupal\Tests\file\Unit;
+
+use Drupal\Component\Transliteration\PhpTransliteration;
+use Drupal\Core\File\Event\FileUploadSanitizeNameEvent;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\file\EventSubscriber\FileEventSubscriber;
+use Drupal\Tests\UnitTestCase;
+
+// cSpell:ignore TÉXT äöüåøhello aouaohello aeoeueaohello Pácê
+
+/**
+ * Filename sanitization tests.
+ *
+ * @group file
+ */
+class SanitizeNameTest extends UnitTestCase {
+
+  /**
+   * Test file name sanitization.
+   *
+   * @param string $original
+   *   The original filename.
+   * @param string $expected
+   *   The expected filename.
+   * @param array $options
+   *   Array of filename sanitization options, in this order:
+   *   0: boolean Transliterate.
+   *   1: string Replace whitespace.
+   *   2: string Replace non-alphanumeric characters.
+   *   3: boolean De-duplicate separators.
+   *   4: boolean Convert to lowercase.
+   * @param string $language_id
+   *   Optional language code for transliteration. Defaults to 'en'.
+   *
+   * @dataProvider provideFilenames
+   *
+   * @covers \Drupal\file\EventSubscriber\FileEventSubscriber::sanitizeFilename
+   * @covers \Drupal\Core\File\Event\FileUploadSanitizeNameEvent::__construct
+   */
+  public function testFileNameTransliteration($original, $expected, array $options, $language_id = 'en') {
+    $sanitization_options = [
+      'transliterate' => $options[0],
+      'replacement_character' => $options[1],
+      'replace_whitespace' => $options[2],
+      'replace_non_alphanumeric' => $options[3],
+      'deduplicate_separators' => $options[4],
+      'lowercase' => $options[5],
+    ];
+    $config_factory = $this->getConfigFactoryStub([
+      'file.settings' => [
+        'filename_sanitization' => $sanitization_options,
+      ],
+    ]);
+
+    $language = new Language(['id' => $language_id]);
+    $language_manager = $this->prophesize(LanguageManagerInterface::class);
+    $language_manager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->willReturn($language);
+
+    $event = new FileUploadSanitizeNameEvent($original, $language_id);
+    $subscriber = new FileEventSubscriber($config_factory, new PhpTransliteration(), $language_manager->reveal());
+    $subscriber->sanitizeFilename($event);
+
+    // Check the results of the configured sanitization.
+    $this->assertEquals($expected, $event->getFilename());
+  }
+
+  /**
+   * Provides data for testFileNameTransliteration().
+   *
+   * @return array
+   *   Arrays with original name, expected name, and sanitization options.
+   */
+  public function provideFilenames() {
+    return [
+      'Test default options' => [
+        'TÉXT-œ.txt',
+        'TÉXT-œ.txt',
+        [FALSE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Test raw file without extension' => [
+        'TÉXT-œ',
+        'TÉXT-œ',
+        [FALSE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Test only transliteration: simple' => [
+        'Á-TÉXT-œ.txt',
+        'A-TEXT-oe.txt',
+        [TRUE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Test only transliteration: raw file without extension' => [
+        'Á-TÉXT-œ',
+        'A-TEXT-oe',
+        [TRUE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Test only transliteration: complex and replace (-)' => [
+        'S  Pácê--táb#	#--🙈.jpg',
+        'S  Pace--tab#	#---.jpg',
+        [TRUE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Test only transliteration: complex and replace (_)' => [
+        'S  Pácê--táb#	#--🙈.jpg',
+        'S  Pace--tab#	#--_.jpg',
+        [TRUE, '_', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Test transliteration, replace (-) and replace whitespace (trim front)' => [
+        '  S  Pácê--táb#	#--🙈.png',
+        'S--Pace--tab#-#---.png',
+        [TRUE, '-', TRUE, FALSE, FALSE, FALSE],
+      ],
+      'Test transliteration, replace (-) and replace whitespace (trim both sides)' => [
+        '  S  Pácê--táb#	#--🙈   .jpg',
+        'S--Pace--tab#-#---.jpg',
+        [TRUE, '-', TRUE, FALSE, FALSE, FALSE],
+      ],
+      'Test transliteration, replace (_) and replace whitespace (trim both sides)' => [
+        '  S  Pácê--táb#	#--🙈  .jpg',
+        'S__Pace--tab#_#--_.jpg',
+        [TRUE, '_', TRUE, FALSE, FALSE, FALSE],
+      ],
+      'Test transliteration, replace (_), replace whitespace and replace non-alphanumeric' => [
+        '  S  Pácê--táb#	#--🙈.txt',
+        'S__Pace--tab___--_.txt',
+        [TRUE, '_', TRUE, TRUE, FALSE, FALSE],
+      ],
+      'Test transliteration, replace (-), replace whitespace and replace non-alphanumeric' => [
+        '  S  Pácê--táb#	#--🙈.txt',
+        'S--Pace--tab------.txt',
+        [TRUE, '-', TRUE, TRUE, FALSE, FALSE],
+      ],
+      'Test transliteration, replace (-), replace whitespace, replace non-alphanumeric and removing duplicate separators' => [
+        'S  Pácê--táb#	#--🙈.txt',
+        'S-Pace-tab.txt',
+        [TRUE, '-', TRUE, TRUE, TRUE, FALSE],
+      ],
+      'Test transliteration, replace (-), replace whitespace and deduplicate separators' => [
+        '  S  Pácê--táb#	#--🙈.txt',
+        'S-Pace-tab#-#.txt',
+        [TRUE, '-', TRUE, FALSE, TRUE, FALSE],
+      ],
+      'Test transliteration, replace (_), replace whitespace, replace non-alphanumeric and deduplicate separators' => [
+        'S  Pácê--táb#	#--🙈.txt',
+        'S_Pace_tab.txt',
+        [TRUE, '_', TRUE, TRUE, TRUE, FALSE],
+      ],
+      'Test transliteration, replace (-), replace whitespace, replace non-alphanumeric, deduplicate separators and lowercase conversion' => [
+        'S  Pácê--táb#	#--🙈.jpg',
+        's-pace-tab.jpg',
+        [TRUE, '-', TRUE, TRUE, TRUE, TRUE],
+      ],
+      'Test transliteration, replace (_), replace whitespace, replace non-alphanumeric, deduplicate separators and lowercase conversion' => [
+        'S  Pácê--táb#	#--🙈.txt',
+        's_pace_tab.txt',
+        [TRUE, '_', TRUE, TRUE, TRUE, TRUE],
+      ],
+      'Ignore non-alphanumeric replacement if transliteration is not set, but still replace whitespace, deduplicate separators, and lowercase' => [
+        '  2S  Pácê--táb#	#--🙈.txt',
+        '2s-pácê-táb#-#-🙈.txt',
+        [FALSE, '-', TRUE, TRUE, TRUE, TRUE],
+      ],
+      'Only lowercase, simple' => [
+        'TEXT.txt',
+        'text.txt',
+        [TRUE, '-', FALSE, FALSE, FALSE, TRUE],
+      ],
+      'Only lowercase, with unicode' => [
+        'TÉXT.txt',
+        'text.txt',
+        [TRUE, '-', FALSE, FALSE, FALSE, TRUE],
+      ],
+      'No transformations' => [
+        'Ä Ö Ü Å Ø äöüåøhello.txt',
+        'Ä Ö Ü Å Ø äöüåøhello.txt',
+        [FALSE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Transliterate via en (not de), no other transformations' => [
+        'Ä Ö Ü Å Ø äöüåøhello.txt',
+        'A O U A O aouaohello.txt',
+        [TRUE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Transliterate via de (not en), no other transformations' => [
+        'Ä Ö Ü Å Ø äöüåøhello.txt',
+        'Ae Oe Ue A O aeoeueaohello.txt',
+        [TRUE, '-', FALSE, FALSE, FALSE, FALSE], 'de',
+      ],
+      'Transliterate via de not en, plus whitespace + lowercase' => [
+        'Ä Ö Ü Å Ø äöüåøhello.txt',
+        'ae-oe-ue-a-o-aeoeueaohello.txt',
+        [TRUE, '-', TRUE, FALSE, FALSE, TRUE], 'de',
+      ],
+      'Remove duplicate separators with falsey extension' => [
+        'foo.....0',
+        'foo.0',
+        [TRUE, '-', FALSE, FALSE, TRUE, FALSE],
+      ],
+      'Remove duplicate separators with extension and ending in dot' => [
+        'foo.....txt',
+        'foo.txt',
+        [TRUE, '-', FALSE, FALSE, TRUE, FALSE],
+      ],
+      'Remove duplicate separators without extension and ending in dot' => [
+        'foo.....',
+        'foo',
+        [TRUE, '-', FALSE, FALSE, TRUE, FALSE],
+      ],
+      'All unknown unicode' => [
+        '🙈🙈🙈.txt',
+        '---.txt',
+        [TRUE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      '✓ unicode' => [
+        '✓.txt',
+        '-.txt',
+        [TRUE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Multiple ✓ unicode' => [
+        '✓✓✓.txt',
+        '---.txt',
+        [TRUE, '-', FALSE, FALSE, FALSE, FALSE],
+      ],
+      'Test transliteration, replace (-), replace whitespace and removing multiple duplicate separators #1' => [
+        'Test_-_file.png',
+        'test-file.png',
+        [TRUE, '-', TRUE, TRUE, TRUE, TRUE],
+      ],
+      'Test transliteration, replace (-), replace whitespace and removing multiple duplicate separators #2' => [
+        'Test .. File.png',
+        'test-file.png',
+        [TRUE, '-', TRUE, TRUE, TRUE, TRUE],
+      ],
+      'Test transliteration, replace (-), replace whitespace and removing multiple duplicate separators #3' => [
+        'Test -..__-- file.png',
+        'test-file.png',
+        [TRUE, '-', TRUE, TRUE, TRUE, TRUE],
+      ],
+      'Test transliteration, replace (-), replace sequences of dots, underscores and/or dashes with the replacement character' => [
+        'abc. --_._-- .abc.jpeg',
+        'abc. - .abc.jpeg',
+        [TRUE, '-', FALSE, FALSE, TRUE, FALSE],
+      ],
+    ];
+  }
+
+}
diff --git a/core/modules/filter/src/Plugin/Filter/FilterHtml.php b/core/modules/filter/src/Plugin/Filter/FilterHtml.php
index 88cf3484a0aad5ad973ad8c1fa80926285e73e6b..345ae193dea9d0c88eb0bd728a89af3d4f513c1c 100644
--- a/core/modules/filter/src/Plugin/Filter/FilterHtml.php
+++ b/core/modules/filter/src/Plugin/Filter/FilterHtml.php
@@ -250,22 +250,17 @@ public function getHTMLRestrictions() {
     // Parse the allowed HTML setting, and gradually make the list of allowed
     // tags more specific.
     $restrictions = ['allowed' => []];
+    $html = $this->settings['allowed_html'];
 
-    // Make all the tags self-closing, so they will be parsed into direct
-    // children of the body tag in the DomDocument.
-    $html = str_replace('>', ' />', $this->settings['allowed_html']);
     // Protect any trailing * characters in attribute names, since DomDocument
     // strips them as invalid.
     // cSpell:disable-next-line
     $star_protector = '__zqh6vxfbk3cg__';
     $html = str_replace('*', $star_protector, $html);
-    $body_child_nodes = Html::load($html)->getElementsByTagName('body')->item(0)->childNodes;
 
-    foreach ($body_child_nodes as $node) {
-      if ($node->nodeType !== XML_ELEMENT_NODE) {
-        // Skip the empty text nodes inside tags.
-        continue;
-      }
+    $dom = Html::load($html);
+    $xpath = new \DOMXPath($dom);
+    foreach ($xpath->query('//body//*') as $node) {
       $tag = $node->tagName;
 
       // All attributes are already allowed on this tag, this is the most
diff --git a/core/modules/filter/tests/src/Functional/GenericTest.php b/core/modules/filter/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d8948a0aca69a86605822d1fa986fbe2aad2e2b4
--- /dev/null
+++ b/core/modules/filter/tests/src/Functional/GenericTest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Tests\filter\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for filter.
+ *
+ * @group filter
+ */
+class GenericTest extends GenericModuleTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function preUninstallSteps(): void {
+    $storage = \Drupal::entityTypeManager()->getStorage('filter_format');
+    $text_formats = $storage->loadMultiple();
+    $storage->delete($text_formats);
+  }
+
+}
diff --git a/core/modules/filter/tests/src/Kernel/FilterCaptionTwigDebugTest.php b/core/modules/filter/tests/src/Kernel/FilterCaptionTwigDebugTest.php
index 6ebffd1f810a89f2c1a6803076459598fa194fba..eaeca4304d25fc6f9011ed99e921ce3dd825cca7 100644
--- a/core/modules/filter/tests/src/Kernel/FilterCaptionTwigDebugTest.php
+++ b/core/modules/filter/tests/src/Kernel/FilterCaptionTwigDebugTest.php
@@ -53,7 +53,7 @@ public function testCaptionFilter() {
 
     // Data-caption attribute.
     $input = '<img src="llama.jpg" data-caption="Loquacious llama!" />';
-    $expected = '<img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption>';
+    $expected = '<img src="llama.jpg">' . "\n" . '<figcaption>Loquacious llama!</figcaption>';
     $output = $test($input)->getProcessedText();
     $this->assertStringContainsString($expected, $output);
     $this->assertStringContainsString("<!-- THEME HOOK: 'filter_caption' -->", $output);
diff --git a/core/modules/filter/tests/src/Kernel/FilterKernelTest.php b/core/modules/filter/tests/src/Kernel/FilterKernelTest.php
index 0fa0d28854600294e6bc08fc02da1642a4f21d64..ff20df0c95fc6bd366c730c4b913b63431f7a674 100644
--- a/core/modules/filter/tests/src/Kernel/FilterKernelTest.php
+++ b/core/modules/filter/tests/src/Kernel/FilterKernelTest.php
@@ -60,40 +60,40 @@ public function testAlignFilter() {
 
     // Data-align attribute: all 3 allowed values.
     $input = '<img src="llama.jpg" data-align="left" />';
-    $expected = '<img src="llama.jpg" class="align-left" />';
+    $expected = '<img src="llama.jpg" class="align-left">';
     $this->assertSame($expected, $test($input)->getProcessedText());
     $input = '<img src="llama.jpg" data-align="center" />';
-    $expected = '<img src="llama.jpg" class="align-center" />';
+    $expected = '<img src="llama.jpg" class="align-center">';
     $this->assertSame($expected, $test($input)->getProcessedText());
     $input = '<img src="llama.jpg" data-align="right" />';
-    $expected = '<img src="llama.jpg" class="align-right" />';
+    $expected = '<img src="llama.jpg" class="align-right">';
     $this->assertSame($expected, $test($input)->getProcessedText());
 
     // Data-align attribute: a disallowed value.
     $input = '<img src="llama.jpg" data-align="left foobar" />';
-    $expected = '<img src="llama.jpg" />';
+    $expected = '<img src="llama.jpg">';
     $this->assertSame($expected, $test($input)->getProcessedText());
 
     // Empty data-align attribute.
     $input = '<img src="llama.jpg" data-align="" />';
-    $expected = '<img src="llama.jpg" />';
+    $expected = '<img src="llama.jpg">';
     $this->assertSame($expected, $test($input)->getProcessedText());
 
     // Ensure the filter also works with uncommon yet valid attribute quoting.
     $input = '<img src=llama.jpg data-align=right />';
-    $expected = '<img src="llama.jpg" class="align-right" />';
+    $expected = '<img src="llama.jpg" class="align-right">';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
 
     // Security test: attempt to inject an additional class.
     $input = '<img src="llama.jpg" data-align="center another-class-here" />';
-    $expected = '<img src="llama.jpg" />';
+    $expected = '<img src="llama.jpg">';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
 
     // Security test: attempt an XSS.
     $input = '<img src="llama.jpg" data-align="center \'onclick=\'alert(foo);" />';
-    $expected = '<img src="llama.jpg" />';
+    $expected = '<img src="llama.jpg">';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
   }
@@ -125,26 +125,38 @@ public function testCaptionFilter() {
 
     // Data-caption attribute.
     $input = '<img src="llama.jpg" data-caption="Loquacious llama!" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
 
     // Empty data-caption attribute.
     $input = '<img src="llama.jpg" data-caption="" />';
-    $expected = '<img src="llama.jpg" />';
+    $expected = '<img src="llama.jpg">';
     $this->assertSame($expected, $test($input)->getProcessedText());
 
     // HTML entities in the caption.
     $input = '<img src="llama.jpg" data-caption="&ldquo;Loquacious llama!&rdquo;" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>“Loquacious llama!”</figcaption></figure>';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>“Loquacious llama!”</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
 
     // HTML encoded as HTML entities in data-caption attribute.
     $input = '<img src="llama.jpg" data-caption="&lt;em&gt;Loquacious llama!&lt;/em&gt;" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption><em>Loquacious llama!</em></figcaption></figure>';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption><em>Loquacious llama!</em></figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
@@ -153,40 +165,64 @@ public function testCaptionFilter() {
     // not allowed by the HTML spec, but may happen when people manually write
     // HTML, so we explicitly support it.
     $input = '<img src="llama.jpg" data-caption="<em>Loquacious llama!</em>" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption><em>Loquacious llama!</em></figcaption></figure>';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption><em>Loquacious llama!</em></figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
 
     // Security test: attempt an XSS.
     $input = '<img src="llama.jpg" data-caption="<script>alert(\'Loquacious llama!\')</script>" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>alert(\'Loquacious llama!\')</figcaption></figure>';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>alert(\'Loquacious llama!\')</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
 
     // Ensure the filter also works with uncommon yet valid attribute quoting.
     $input = '<img src=llama.jpg data-caption=\'Loquacious llama!\' />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
 
     // Finally, ensure that this also works on any other tag.
     $input = '<video src="llama.jpg" data-caption="Loquacious llama!" />';
-    $expected = '<figure role="group"><video src="llama.jpg"></video><figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group">
+<video src="llama.jpg"></video>
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
     $input = '<foobar data-caption="Loquacious llama!">baz</foobar>';
-    $expected = '<figure role="group"><foobar>baz</foobar><figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group">
+<foobar>baz</foobar>
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
 
     // Ensure the caption filter works for linked images.
     $input = '<a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" data-caption="Loquacious llama!" /></a>';
-    $expected = '<figure role="group"><a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" /></a>' . "\n" . '<figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group">
+<a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg"></a>
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
@@ -219,46 +255,74 @@ public function testCaptionFilter() {
 
     // All the tricky cases encountered at https://www.drupal.org/node/2105841.
     // A plain URL preceded by text.
-    $input = '<img data-caption="See https://www.drupal.org" src="llama.jpg" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>See https://www.drupal.org</figcaption></figure>';
+    $input = '<img data-caption="See https://www.drupal.org" src="llama.jpg">';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>See https://www.drupal.org</figcaption>
+</figure>
+';
     $this->assertSame($expected, $test_with_html_filter($input));
     $this->assertSame($input, $test_editor_xss_filter($input));
 
     // An anchor.
-    $input = '<img data-caption="This is a &lt;a href=&quot;https://www.drupal.org&quot;&gt;quick&lt;/a&gt; test…" src="llama.jpg" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>This is a <a href="https://www.drupal.org">quick</a> test…</figcaption></figure>';
+    $input = '<img data-caption="This is a &lt;a href=&quot;https://www.drupal.org&quot;&gt;quick&lt;/a&gt; test…" src="llama.jpg">';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>This is a <a href="https://www.drupal.org">quick</a> test…</figcaption>
+</figure>
+';
     $this->assertSame($expected, $test_with_html_filter($input));
     $this->assertSame($input, $test_editor_xss_filter($input));
 
     // A plain URL surrounded by parentheses.
-    $input = '<img data-caption="(https://www.drupal.org)" src="llama.jpg" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>(https://www.drupal.org)</figcaption></figure>';
+    $input = '<img data-caption="(https://www.drupal.org)" src="llama.jpg">';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>(https://www.drupal.org)</figcaption>
+</figure>
+';
     $this->assertSame($expected, $test_with_html_filter($input));
     $this->assertSame($input, $test_editor_xss_filter($input));
 
     // A source being credited.
-    $input = '<img data-caption="Source: Wikipedia" src="llama.jpg" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>Source: Wikipedia</figcaption></figure>';
+    $input = '<img data-caption="Source: Wikipedia" src="llama.jpg">';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>Source: Wikipedia</figcaption>
+</figure>
+';
     $this->assertSame($expected, $test_with_html_filter($input));
     $this->assertSame($input, $test_editor_xss_filter($input));
 
     // A source being credited, without a space after the colon.
-    $input = '<img data-caption="Source:Wikipedia" src="llama.jpg" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>Source:Wikipedia</figcaption></figure>';
+    $input = '<img data-caption="Source:Wikipedia" src="llama.jpg">';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>Source:Wikipedia</figcaption>
+</figure>
+';
     $this->assertSame($expected, $test_with_html_filter($input));
     $this->assertSame($input, $test_editor_xss_filter($input));
 
     // A pretty crazy edge case where we have two colons.
-    $input = '<img data-caption="Interesting (Scope resolution operator ::)" src="llama.jpg" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>Interesting (Scope resolution operator ::)</figcaption></figure>';
+    $input = '<img data-caption="Interesting (Scope resolution operator ::)" src="llama.jpg">';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>Interesting (Scope resolution operator ::)</figcaption>
+</figure>
+';
     $this->assertSame($expected, $test_with_html_filter($input));
     $this->assertSame($input, $test_editor_xss_filter($input));
 
     // An evil anchor (to ensure XSS filtering is applied to the caption also).
-    $input = '<img data-caption="This is an &lt;a href=&quot;javascript:alert();&quot;&gt;evil&lt;/a&gt; test…" src="llama.jpg" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>This is an <a href="alert();">evil</a> test…</figcaption></figure>';
+    $input = '<img data-caption="This is an &lt;a href=&quot;javascript:alert();&quot;&gt;evil&lt;/a&gt; test…" src="llama.jpg">';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>This is an <a href="alert();">evil</a> test…</figcaption>
+</figure>
+';
     $this->assertSame($expected, $test_with_html_filter($input));
-    $expected_xss_filtered = '<img data-caption="This is an &lt;a href=&quot;alert();&quot;&gt;evil&lt;/a&gt; test…" src="llama.jpg" />';
+    $expected_xss_filtered = '<img data-caption="This is an &lt;a href=&quot;alert();&quot;&gt;evil&lt;/a&gt; test…" src="llama.jpg">';
     $this->assertSame($expected_xss_filtered, $test_editor_xss_filter($input));
   }
 
@@ -286,17 +350,29 @@ public function testAlignAndCaptionFilters() {
     // Both data-caption and data-align attributes: all 3 allowed values for the
     // data-align attribute.
     $input = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="left" />';
-    $expected = '<figure role="group" class="align-left"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group" class="align-left">
+<img src="llama.jpg">
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
     $input = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="center" />';
-    $expected = '<figure role="group" class="align-center"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group" class="align-center">
+<img src="llama.jpg">
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
     $input = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="right" />';
-    $expected = '<figure role="group" class="align-right"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group" class="align-right">
+<img src="llama.jpg">
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
@@ -304,14 +380,22 @@ public function testAlignAndCaptionFilters() {
     // Both data-caption and data-align attributes, but a disallowed data-align
     // attribute value.
     $input = '<img src="llama.jpg" data-caption="Loquacious llama!" data-align="left foobar" />';
-    $expected = '<figure role="group"><img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group">
+<img src="llama.jpg">
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
 
     // Ensure both filters together work for linked images.
     $input = '<a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" data-caption="Loquacious llama!" data-align="center" /></a>';
-    $expected = '<figure role="group" class="align-center"><a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" /></a>' . "\n" . '<figcaption>Loquacious llama!</figcaption></figure>';
+    $expected = '<figure role="group" class="align-center">
+<a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg"></a>
+<figcaption>Loquacious llama!</figcaption>
+</figure>
+';
     $output = $test($input);
     $this->assertSame($expected, $output->getProcessedText());
     $this->assertSame($attached_library, $output->getAttachments());
@@ -455,14 +539,13 @@ public function testHtmlFilter() {
 
     $f = (string) $filter->process('<code onerror>&nbsp;</code>', Language::LANGCODE_NOT_SPECIFIED);
     $this->assertNoNormalized($f, 'onerror', 'HTML filter should remove empty on* attributes.');
-    // Note - this string has a decoded &nbsp; character.
-    $this->assertSame('<code> </code>', $f);
+    $this->assertSame('<code>&nbsp;</code>', $f);
 
     $f = (string) $filter->process('<br>', Language::LANGCODE_NOT_SPECIFIED);
-    $this->assertNormalized($f, '<br />', 'HTML filter should allow line breaks.');
+    $this->assertNormalized($f, '<br>', 'HTML filter should allow line breaks.');
 
     $f = (string) $filter->process('<br />', Language::LANGCODE_NOT_SPECIFIED);
-    $this->assertNormalized($f, '<br />', 'HTML filter should allow self-closing line breaks.');
+    $this->assertNormalized($f, '<br>', 'HTML filter should allow self-closing line breaks.');
 
     // All attributes of allowed tags are stripped by default.
     $f = (string) $filter->process('<a kitten="cute" llama="awesome">link</a>', Language::LANGCODE_NOT_SPECIFIED);
@@ -934,9 +1017,8 @@ public function testHtmlCorrectorFilter() {
     $f = Html::normalize('<div id="d">content');
     $this->assertEquals('<div id="d">content</div>', $f, 'HTML corrector -- unclosed tag with attribute.');
 
-    // XHTML slash for empty elements.
     $f = Html::normalize('<hr><br>');
-    $this->assertEquals('<hr /><br />', $f, 'HTML corrector -- XHTML closing slash.');
+    $this->assertEquals('<hr><br>', $f, 'HTML corrector -- void element.');
 
     $f = Html::normalize('<P>test</P>');
     $this->assertEquals('<p>test</p>', $f, 'HTML corrector -- Convert uppercased tags to proper lowercased ones.');
@@ -945,37 +1027,37 @@ public function testHtmlCorrectorFilter() {
     $this->assertEquals('<p>test</p>', $f, 'HTML corrector -- Convert uppercased tags to proper lowercased ones.');
 
     $f = Html::normalize('test<hr />');
-    $this->assertEquals('test<hr />', $f, 'HTML corrector -- Let proper XHTML pass through.');
+    $this->assertEquals('test<hr>', $f, 'HTML corrector -- convert self-closing element to HTML5 void element.');
 
     $f = Html::normalize('test<hr/>');
-    $this->assertEquals('test<hr />', $f, 'HTML corrector -- Let proper XHTML pass through, but ensure there is a single space before the closing slash.');
+    $this->assertEquals('test<hr>', $f, 'HTML corrector -- convert self-closing element to HTML5 void element.');
 
     $f = Html::normalize('test<hr    />');
-    $this->assertEquals('test<hr />', $f, 'HTML corrector -- Let proper XHTML pass through, but ensure there are not too many spaces before the closing slash.');
+    $this->assertEquals('test<hr>', $f, 'HTML corrector -- convert self-closing element with multiple spaces to HTML5 void element.');
 
     $f = Html::normalize('<span class="test" />');
     $this->assertEquals('<span class="test"></span>', $f, 'HTML corrector -- Convert XHTML that is properly formed but that would not be compatible with typical HTML user agents.');
 
     $f = Html::normalize('test1<br class="test">test2');
-    $this->assertEquals('test1<br class="test" />test2', $f, 'HTML corrector -- Automatically close single tags.');
+    $this->assertEquals('test1<br class="test">test2', $f, 'HTML corrector -- Keep self-closing tags.');
 
     $f = Html::normalize('line1<hr>line2');
-    $this->assertEquals('line1<hr />line2', $f, 'HTML corrector -- Automatically close single tags.');
+    $this->assertEquals('line1<hr>line2', $f, 'HTML corrector -- Keep self-closing tags.');
 
     $f = Html::normalize('line1<HR>line2');
-    $this->assertEquals('line1<hr />line2', $f, 'HTML corrector -- Automatically close single tags.');
+    $this->assertEquals('line1<hr>line2', $f, 'HTML corrector -- Keep self-closing tags.');
 
     $f = Html::normalize('<img src="http://example.com/test.jpg">test</img>');
-    $this->assertEquals('<img src="http://example.com/test.jpg" />test', $f, 'HTML corrector -- Automatically close single tags.');
+    $this->assertEquals('<img src="http://example.com/test.jpg">test', $f, 'HTML corrector -- Fix self-closing single tags.');
 
     $f = Html::normalize('<br></br>');
-    $this->assertEquals('<br />', $f, "HTML corrector -- Transform empty tags to a single closed tag if the tag's content model is EMPTY.");
+    $this->assertEquals('<br><br>', $f, "HTML corrector -- Transform empty tags to a self-closed tag if the tag's content model is EMPTY.");
 
     $f = Html::normalize('<div></div>');
     $this->assertEquals('<div></div>', $f, "HTML corrector -- Do not transform empty tags to a single closed tag if the tag's content model is not EMPTY.");
 
     $f = Html::normalize('<p>line1<br/><hr/>line2</p>');
-    $this->assertEquals('<p>line1<br /></p><hr />line2', $f, 'HTML corrector -- Move non-inline elements outside of inline containers.');
+    $this->assertEquals('<p>line1<br></p><hr>line2', $f, 'HTML corrector -- Move non-inline elements outside of inline containers.');
 
     $f = Html::normalize('<p>line1<div>line2</div></p>');
     $this->assertEquals('<p>line1</p><div>line2</div>', $f, 'HTML corrector -- Move non-inline elements outside of inline containers.');
@@ -984,7 +1066,7 @@ public function testHtmlCorrectorFilter() {
     $this->assertEquals('<p>test</p><p>test</p>\n', $f, 'HTML corrector -- Auto-close improperly nested tags.');
 
     $f = Html::normalize('<p>Line1<br><STRONG>bold stuff</b>');
-    $this->assertEquals('<p>Line1<br /><strong>bold stuff</strong></p>', $f, 'HTML corrector -- Properly close unclosed tags, and remove useless closing tags.');
+    $this->assertEquals('<p>Line1<br><strong>bold stuff</strong></p>', $f, 'HTML corrector -- Properly close unclosed tags, and remove useless closing tags.');
 
     $f = Html::normalize('test <!-- this is a comment -->');
     $this->assertEquals('test <!-- this is a comment -->', $f, 'HTML corrector -- Do not touch HTML comments.');
@@ -1013,112 +1095,59 @@ public function testHtmlCorrectorFilter() {
     $this->assertEquals('<p>دروبال</p>', $f, 'HTML corrector -- Encoding is correctly kept.');
     // cSpell:enable
 
-    $f = Html::normalize('<script>alert("test")</script>');
-    $this->assertEquals('<script>
-//<![CDATA[
-alert("test")
-//]]>
-</script>', $f, 'HTML corrector -- CDATA added to script element');
+    $html = '<script>alert("test")</script>';
+    $this->assertEquals($html, Html::normalize($html), 'HTML corrector -- script element');
 
-    $f = Html::normalize('<p><script>alert("test")</script></p>');
-    $this->assertEquals('<p><script>
-//<![CDATA[
-alert("test")
-//]]>
-</script></p>', $f, 'HTML corrector -- CDATA added to a nested script element');
+    $html = '<p><script>alert("test")</script></p>';
+    $this->assertEquals($html, Html::normalize($html), 'HTML corrector -- nested script element');
 
-    $f = Html::normalize('<p><style> /* Styling */ body {color:red}</style></p>');
-    $this->assertEquals('<p><style>
-/*<![CDATA[*/
- /* Styling */ body {color:red}
-/*]]>*/
-</style></p>', $f, 'HTML corrector -- CDATA added to a style element.');
+    $html = '<p><style> /* Styling */ body {color:red}</style></p>';
+    $this->assertEquals($html, Html::normalize($html), 'HTML corrector -- style element.');
 
-    $filtered_data = Html::normalize('<p><style>
-/*<![CDATA[*/
-/* Styling */
-body {color:red}
-/*]]>*/
-</style></p>');
-    $this->assertEquals('<p><style>
+    $html = '<p><style>
 /*<![CDATA[*/
 /* Styling */
 body {color:red}
 /*]]>*/
-</style></p>', $filtered_data,
-      new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '/*<![CDATA[*/'])
-    );
+</style></p>';
+    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '/*<![CDATA[*/']));
 
-    $filtered_data = Html::normalize('<p><style>
-/*<![CDATA[*/
-  /* Styling */
-  body {color:red}
-/*]]>*/
-</style></p>');
-    $this->assertEquals('<p><style>
+    $html = '<p><style>
 /*<![CDATA[*/
   /* Styling */
   body {color:red}
 /*]]>*/
-</style></p>', $filtered_data,
-      new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '<!--/*--><![CDATA[/* ><!--*/'])
-    );
+</style></p>';
+    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '<!--/*--><![CDATA[/* ><!--*/']));
 
-    $filtered_data = Html::normalize('<p><script>
+    $html = '<p><script>
 //<![CDATA[
   alert("test");
 //]]>
-</script></p>');
-    $this->assertEquals('<p><script>
-//<![CDATA[
-  alert("test");
-//]]>
-</script></p>', $filtered_data,
-      new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '<!--//--><![CDATA[// ><!--'])
-    );
+</script></p>';
+    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '<!--//--><![CDATA[// ><!--']));
 
-    $filtered_data = Html::normalize('<p><script>
+    $html = '<p><script>
 // <![CDATA[
   alert("test");
 //]]>
-</script></p>');
-    $this->assertEquals('<p><script>
-// <![CDATA[
-  alert("test");
-//]]>
-</script></p>', $filtered_data,
-      new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA['])
-    );
+</script></p>';
+    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA[']));
 
-    $filtered_data = Html::normalize('<p><script>
-// <![CDATA[![CDATA[![CDATA[
-  alert("test");
-//]]]]]]>
-</script></p>');
-    $this->assertEquals('<p><script>
+    $html = '<p><script>
 // <![CDATA[![CDATA[![CDATA[
   alert("test");
 //]]]]]]>
-</script></p>', $filtered_data,
-      new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA[![CDATA[![CDATA['])
-    );
+</script></p>';
+    $this->assertEquals($html, Html::normalize($html), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA[![CDATA[![CDATA[']));
 
     // Test calling Html::normalize() twice.
-    $filtered_data = Html::normalize('<p><script>
+    $html = '<p><script>
 // <![CDATA[![CDATA[![CDATA[
   alert("test");
 //]]]]]]>
-</script></p>');
-    $filtered_data = Html::normalize($filtered_data);
-
-    $this->assertEquals('<p><script>
-// <![CDATA[![CDATA[![CDATA[
-  alert("test");
-//]]]]]]>
-</script></p>', $filtered_data,
-      new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA[![CDATA[![CDATA['])
-    );
-
+</script></p>';
+    $this->assertEquals($html, Html::normalize(Html::normalize($html)), new FormattableMarkup('HTML corrector -- Existing cdata section @pattern_name properly escaped', ['@pattern_name' => '// <![CDATA[![CDATA[![CDATA[']));
   }
 
   /**
diff --git a/core/modules/filter/tests/src/Unit/FilterHtmlTest.php b/core/modules/filter/tests/src/Unit/FilterHtmlTest.php
index 56740c8b3bef275e5861ebfa51ba7c4fb3c2f088..7ffb1691dd098d1f8fb2193b6bfbe953d6efe91b 100644
--- a/core/modules/filter/tests/src/Unit/FilterHtmlTest.php
+++ b/core/modules/filter/tests/src/Unit/FilterHtmlTest.php
@@ -56,9 +56,7 @@ public function providerFilterAttributes() {
       ['<p dir="rtl" />', '<p dir="rtl"></p>'],
       ['<p dir="bogus" />', '<p></p>'],
       ['<p id="first" />', '<p></p>'],
-      // The addition of xml:lang isn't especially desired, but is still valid
-      // HTML5. See https://www.drupal.org/node/1333730.
-      ['<p id="first" lang="en">text</p>', '<p lang="en" xml:lang="en">text</p>'],
+      ['<p id="first" lang="en">text</p>', '<p lang="en">text</p>'],
       ['<p style="display: none;" />', '<p></p>'],
       ['<code class="pretty invalid">foreach ($a as $b) {}</code>', '<code class="pretty">foreach ($a as $b) {}</code>'],
       ['<code class="boring pretty">foreach ($a as $b) {}</code>', '<code class="boring pretty">foreach ($a as $b) {}</code>'],
diff --git a/core/modules/filter/tests/src/Unit/FilterImageLazyLoadTest.php b/core/modules/filter/tests/src/Unit/FilterImageLazyLoadTest.php
index 6da46a4a55fa1285a245d8daae8fe04f1b38663d..9686beb343a0a77f6de25caccb170fbc5d1ce2c0 100644
--- a/core/modules/filter/tests/src/Unit/FilterImageLazyLoadTest.php
+++ b/core/modules/filter/tests/src/Unit/FilterImageLazyLoadTest.php
@@ -50,27 +50,27 @@ public function providerHtml(): array {
     return [
       'lazy loading attribute already added' => [
         'input' => '<p><img src="foo.png" loading="lazy"></p>',
-        'output' => '<p><img src="foo.png" loading="lazy" /></p>',
+        'output' => '<p><img src="foo.png" loading="lazy"></p>',
       ],
       'eager loading attribute already added' => [
         'input' => '<p><img src="foo.png" loading="eager"/></p>',
-        'output' => '<p><img src="foo.png" loading="eager" /></p>',
+        'output' => '<p><img src="foo.png" loading="eager"></p>',
       ],
       'image dimensions provided' => [
         'input' => '<p><img src="foo.png" width="200" height="200"/></p>',
-        '<p><img src="foo.png" width="200" height="200" loading="lazy" /></p>',
+        'output' => '<p><img src="foo.png" width="200" height="200" loading="lazy"></p>',
       ],
       'width image dimensions provided' => [
         'input' => '<p><img src="foo.png" width="200"/></p>',
-        '<p><img src="foo.png" width="200" /></p>',
+        'output' => '<p><img src="foo.png" width="200"></p>',
       ],
       'height image dimensions provided' => [
         'input' => '<p><img src="foo.png" height="200"/></p>',
-        '<p><img src="foo.png" height="200" /></p>',
+        'output' => '<p><img src="foo.png" height="200"></p>',
       ],
       'invalid loading attribute' => [
         'input' => '<p><img src="foo.png" width="200" height="200" loading="foo"></p>',
-        'output' => '<p><img src="foo.png" width="200" height="200" loading="lazy" /></p>',
+        'output' => '<p><img src="foo.png" width="200" height="200" loading="lazy"></p>',
       ],
       'no image tag' => [
         'input' => '<p>Lorem ipsum...</p>',
@@ -78,7 +78,7 @@ public function providerHtml(): array {
       ],
       'no image dimensions provided' => [
         'input' => '<p><img src="foo.png"></p>',
-        'output' => '<p><img src="foo.png" /></p>',
+        'output' => '<p><img src="foo.png"></p>',
       ],
     ];
   }
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 4c92235d129eb09419eb13b120ec39a6560e4f1e..f7a6edfa9199ebafb06954acd2c7b9c9ddb7dc10 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -13,6 +13,17 @@
 use Drupal\Core\Url;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\language\Plugin\migrate\source\d7\LanguageContentSettingsTaxonomyVocabulary as D7LanguageContentSettingsTaxonomyVocabulary;
+use Drupal\migrate\Plugin\MigrateSourceInterface;
+use Drupal\migrate\Plugin\MigrationInterface;
+use Drupal\migrate\Row;
+use Drupal\taxonomy\Plugin\migrate\source\d6\Term as D6Term;
+use Drupal\taxonomy\Plugin\migrate\source\d6\Vocabulary as D6Vocabulary;
+use Drupal\taxonomy\Plugin\migrate\source\d6\VocabularyPerType as D6VocabularyPerType;
+use Drupal\taxonomy\Plugin\migrate\source\d7\Term as D7Term;
+use Drupal\taxonomy\Plugin\migrate\source\d7\TermEntityTranslation;
+use Drupal\taxonomy\Plugin\migrate\source\d7\Vocabulary as D7Vocabulary;
+use Drupal\taxonomy\Plugin\migrate\source\d7\VocabularyTranslation as D7VocabularyTranslation;
 use Drupal\taxonomy\VocabularyInterface;
 use Drupal\user\Entity\User;
 
@@ -648,3 +659,102 @@ function template_preprocess_forum_submitted(&$variables) {
   }
   $variables['time'] = isset($variables['topic']->created) ? \Drupal::service('date.formatter')->formatTimeDiffSince($variables['topic']->created) : '';
 }
+
+/**
+ * Implements hook_migrate_prepare_row().
+ */
+function forum_migrate_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
+  $source_plugin = $migration->getSourcePlugin();
+  if (is_a($source_plugin, D6Term::class) || is_a($source_plugin, D7Term::class) || is_a($source_plugin, TermEntityTranslation::class)) {
+    $connection = $source_plugin->getDatabase();
+    if ($connection) {
+      if ($connection->schema()->tableExists('variable')) {
+        $query = $connection->select('variable', 'v')
+          ->fields('v', ['value'])
+          ->condition('name', 'forum_containers');
+        $result = $query->execute()->fetchCol();
+        if ($result) {
+          $forum_container_tids = unserialize($result[0], ['allowed_classes' => FALSE]);
+          $current_tid = $row->getSourceProperty('tid');
+          $row->setSourceProperty('is_container', in_array($current_tid, $forum_container_tids));
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_migrate_MIGRATION_ID_prepare_row().
+ */
+function forum_migrate_d7_taxonomy_vocabulary_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
+  // If the vocabulary being migrated is the one defined in the
+  // 'forum_nav_vocabulary' variable, set the 'forum_vocabulary' source
+  // property to true so we know this is the vocabulary used by Forum.
+  $connection = $migration->getSourcePlugin()->getDatabase();
+  if ($connection) {
+    if ($connection->schema()->tableExists('variable')) {
+      $query = $connection->select('variable', 'v')
+        ->fields('v', ['value'])
+        ->condition('name', 'forum_nav_vocabulary');
+      $result = $query->execute()->fetchCol();
+      if ($result) {
+        $forum_nav_vocabulary = unserialize($result[0], ['allowed_classes' => FALSE]);
+        if ($forum_nav_vocabulary == $row->getSourceProperty('vid')) {
+          $row->setSourceProperty('forum_vocabulary', TRUE);
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_migration_plugins_alter().
+ */
+function forum_migration_plugins_alter(array &$migrations) {
+  // Function to append the forum_vocabulary process plugin.
+  $merge_forum_vocabulary = function ($process) {
+    $process[] = [
+      'plugin' => 'forum_vocabulary',
+      'machine_name' => 'forums',
+    ];
+    return $process;
+  };
+  $merge_forum_field_name = function ($process) {
+    $process[] = [
+      'plugin' => 'forum_vocabulary',
+      'machine_name' => 'taxonomy_forums',
+    ];
+    return $process;
+  };
+  foreach ($migrations as $migration_id => $migration) {
+    // Add process for forum_nav_vocabulary.
+    /** @var \Drupal\migrate\Plugin\migrate\source\SqlBase $source_plugin */
+    $source_plugin = \Drupal::service('plugin.manager.migration')
+      ->createStubMigration($migration)
+      ->getSourcePlugin();
+    if (is_a($source_plugin, D6Vocabulary::class)
+      || is_a($source_plugin, D6VocabularyPerType::class)) {
+      if (isset($migration['process']['vid'])) {
+        $migrations[$migration_id]['process']['vid'] = $merge_forum_vocabulary($migration['process']['vid']);
+      }
+      if (isset($migration['process']['field_name'])) {
+        $migrations[$migration_id]['process']['field_name'] = $merge_forum_field_name($migration['process']['field_name']);
+      }
+    }
+
+    if (is_a($source_plugin, D7Vocabulary::class)
+      && !is_a($source_plugin, D7VocabularyTranslation::class)
+      && !is_a($source_plugin, D7LanguageContentSettingsTaxonomyVocabulary::class)) {
+      if (isset($migration['process']['vid'])) {
+        $process[] = $migrations[$migration_id]['process']['vid'];
+        $migrations[$migration_id]['process']['vid'] = $merge_forum_vocabulary($process);
+      }
+    }
+    // Add process for forum_container.
+    if (is_a($source_plugin, D6Term::class)
+      || is_a($source_plugin, D7Term::class)
+      || is_a($source_plugin, TermEntityTranslation::class)) {
+      $migrations[$migration_id]['process']['forum_container'] = 'is_container';
+    }
+  }
+}
diff --git a/core/modules/forum/src/Plugin/migrate/process/ForumVocabulary.php b/core/modules/forum/src/Plugin/migrate/process/ForumVocabulary.php
new file mode 100644
index 0000000000000000000000000000000000000000..44fad9a9a58e657b1c4d675efd3458140131d6aa
--- /dev/null
+++ b/core/modules/forum/src/Plugin/migrate/process/ForumVocabulary.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\forum\Plugin\migrate\process;
+
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * Checks if the vocabulary being migrated is the one used for forums.
+ *
+ * Drupal 8 Forum is expecting specific machine names for its field and
+ * vocabulary names. This process plugin forces a given machine name to the
+ * field or vocabulary that is being migrated.
+ *
+ * The 'forum_vocabulary' source property is evaluated in the
+ * d6_taxonomy_vocabulary or d7_taxonomy_vocabulary source plugins and is set to
+ * true if the vocabulary vid being migrated is the same as the one in the
+ * 'forum_nav_vocabulary' variable on the source site.
+ *
+ * Example:
+ *
+ * @code
+ * process:
+ *   field_name:
+ *     plugin: forum_vocabulary
+ *     machine_name: taxonomy_forums
+ * @endcode
+ *
+ * @MigrateProcessPlugin(
+ *   id = "forum_vocabulary"
+ * )
+ */
+class ForumVocabulary extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    if ($row->getSourceProperty('forum_vocabulary') && !empty($this->configuration['machine_name'])) {
+      $value = $this->configuration['machine_name'];
+    }
+    return $value;
+  }
+
+}
diff --git a/core/modules/forum/tests/fixtures/drupal6.php b/core/modules/forum/tests/fixtures/drupal6.php
index bb44bff4049c29a2c521d4f877aef895f8c1a1bf..b9cd499e015a34304725cf7a0bdf7c7911bd619b 100644
--- a/core/modules/forum/tests/fixtures/drupal6.php
+++ b/core/modules/forum/tests/fixtures/drupal6.php
@@ -4,7 +4,7 @@
  * @file
  * A database agnostic dump for testing purposes.
  *
- * This file was generated by the Drupal 10.1.0-dev db-tools.php script.
+ * This file was generated by the Drupal 11.0-dev db-tools.php script.
  */
 
 use Drupal\Core\Database\Database;
@@ -2047,6 +2047,18 @@
   'active' => '0',
   'locked' => '0',
 ))
+->values(array(
+  'field_name' => 'field_forums',
+  'type' => 'text',
+  'global_settings' => 'a:4:{s:15:"text_processing";s:1:"0";s:10:"max_length";s:0:"";s:14:"allowed_values";s:0:"";s:18:"allowed_values_php";s:0:"";}',
+  'required' => '0',
+  'multiple' => '0',
+  'db_storage' => '1',
+  'module' => 'text',
+  'db_columns' => 'a:1:{s:5:"value";a:5:{s:4:"type";s:4:"text";s:4:"size";s:3:"big";s:8:"not null";b:0;s:8:"sortable";b:1;s:5:"views";b:1;}}',
+  'active' => '1',
+  'locked' => '0',
+))
 ->values(array(
   'field_name' => 'field_sync',
   'type' => 'email',
@@ -2188,6 +2200,18 @@
   'widget_module' => 'nodereference',
   'widget_active' => '0',
 ))
+->values(array(
+  'field_name' => 'field_forums',
+  'type_name' => 'forum',
+  'weight' => '31',
+  'label' => 'forums',
+  'widget_type' => 'text_textfield',
+  'widget_settings' => 'a:4:{s:4:"rows";i:5;s:4:"size";s:2:"60";s:13:"default_value";a:1:{i:0;a:2:{s:5:"value";s:0:"";s:14:"_error_element";s:44:"default_value_widget][field_forums][0][value";}}s:17:"default_value_php";N;}',
+  'display_settings' => 'a:6:{s:5:"label";a:2:{s:6:"format";s:5:"above";s:7:"exclude";i:0;}s:6:"teaser";a:2:{s:6:"format";s:7:"default";s:7:"exclude";i:0;}s:4:"full";a:2:{s:6:"format";s:7:"default";s:7:"exclude";i:0;}i:4;a:2:{s:6:"format";s:7:"default";s:7:"exclude";i:0;}i:2;a:2:{s:6:"format";s:7:"default";s:7:"exclude";i:0;}i:3;a:2:{s:6:"format";s:7:"default";s:7:"exclude";i:0;}}',
+  'description' => '',
+  'widget_module' => 'text',
+  'widget_active' => '1',
+))
 ->values(array(
   'field_name' => 'field_sync',
   'type_name' => 'employee',
@@ -2261,6 +2285,51 @@
   'mysql_character_set' => 'utf8',
 ));
 
+$connection->schema()->createTable('content_type_forum', array(
+  'fields' => array(
+    'vid' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+      'unsigned' => TRUE,
+    ),
+    'nid' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+      'unsigned' => TRUE,
+    ),
+    'field_forums_value' => array(
+      'type' => 'text',
+      'not null' => FALSE,
+      'size' => 'big',
+    ),
+  ),
+  'primary key' => array(
+    'vid',
+  ),
+  'indexes' => array(
+    'nid' => array(
+      'nid',
+    ),
+  ),
+  'mysql_character_set' => 'utf8',
+));
+
+$connection->insert('content_type_forum')
+->fields(array(
+  'vid',
+  'nid',
+  'field_forums_value',
+))
+->values(array(
+  'vid' => '23',
+  'nid' => '20',
+  'field_forums_value' => 'green',
+))
+->execute();
 $connection->schema()->createTable('date_format_locale', array(
   'fields' => array(
     'format' => array(
@@ -6739,6 +6808,11 @@
   'vid' => '22',
   'tid' => '8',
 ))
+->values(array(
+  'nid' => '20',
+  'vid' => '23',
+  'tid' => '10',
+))
 ->execute();
 $connection->schema()->createTable('history', array(
   'fields' => array(
@@ -6787,7 +6861,12 @@
 ->values(array(
   'uid' => '1',
   'nid' => '19',
-  'timestamp' => '1675403296',
+  'timestamp' => '1687738850',
+))
+->values(array(
+  'uid' => '1',
+  'nid' => '20',
+  'timestamp' => '1679803849',
 ))
 ->values(array(
   'uid' => '1',
@@ -10356,9 +10435,9 @@
   'menu_name' => 'navigation',
   'mlid' => '479',
   'plid' => '0',
-  'link_path' => 'upload/js',
-  'router_path' => 'upload/js',
-  'link_title' => '',
+  'link_path' => 'admin/content/node-type/employee/fields/field_sync/remove',
+  'router_path' => 'admin/content/node-type/employee/fields/field_sync/remove',
+  'link_title' => 'Remove field',
   'options' => 'a:0:{}',
   'module' => 'system',
   'hidden' => '-1',
@@ -10382,22 +10461,22 @@
 ->values(array(
   'menu_name' => 'navigation',
   'mlid' => '480',
-  'plid' => '167',
-  'link_path' => 'admin/settings/uploads',
-  'router_path' => 'admin/settings/uploads',
-  'link_title' => 'File uploads',
-  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:45:"Control how files may be attached to content.";}}',
+  'plid' => '0',
+  'link_path' => 'admin/content/node-type/forum/fields/field_forums/remove',
+  'router_path' => 'admin/content/node-type/forum/fields/field_forums/remove',
+  'link_title' => 'Remove field',
+  'options' => 'a:0:{}',
   'module' => 'system',
-  'hidden' => '0',
+  'hidden' => '-1',
   'external' => '0',
   'has_children' => '0',
   'expanded' => '0',
   'weight' => '0',
-  'depth' => '3',
+  'depth' => '1',
   'customized' => '0',
-  'p1' => '144',
-  'p2' => '167',
-  'p3' => '480',
+  'p1' => '480',
+  'p2' => '0',
+  'p3' => '0',
   'p4' => '0',
   'p5' => '0',
   'p6' => '0',
@@ -10410,9 +10489,9 @@
   'menu_name' => 'navigation',
   'mlid' => '481',
   'plid' => '0',
-  'link_path' => 'admin/content/node-type/employee/fields/field_sync/remove',
-  'router_path' => 'admin/content/node-type/employee/fields/field_sync/remove',
-  'link_title' => 'Remove field',
+  'link_path' => 'upload/js',
+  'router_path' => 'upload/js',
+  'link_title' => '',
   'options' => 'a:0:{}',
   'module' => 'system',
   'hidden' => '-1',
@@ -10433,6 +10512,33 @@
   'p9' => '0',
   'updated' => '0',
 ))
+->values(array(
+  'menu_name' => 'navigation',
+  'mlid' => '482',
+  'plid' => '167',
+  'link_path' => 'admin/settings/uploads',
+  'router_path' => 'admin/settings/uploads',
+  'link_title' => 'File uploads',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:45:"Control how files may be attached to content.";}}',
+  'module' => 'system',
+  'hidden' => '0',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '3',
+  'customized' => '0',
+  'p1' => '144',
+  'p2' => '167',
+  'p3' => '482',
+  'p4' => '0',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
 ->execute();
 $connection->schema()->createTable('menu_router', array(
   'fields' => array(
@@ -12736,6 +12842,50 @@
   'weight' => '1',
   'file' => 'sites/all/modules/cck/includes/content.admin.inc',
 ))
+->values(array(
+  'path' => 'admin/content/node-type/forum/fields/field_forums',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:3:{i:0;s:23:"content_field_edit_form";i:1;s:5:"forum";i:2;s:12:"field_forums";}',
+  'fit' => '63',
+  'number_parts' => '6',
+  'tab_parent' => 'admin/content/node-type/forum/fields',
+  'tab_root' => 'admin/content/node-type/forum',
+  'title' => 'forums',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'type' => '128',
+  'block_callback' => '',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'file' => 'sites/all/modules/cck/includes/content.admin.inc',
+))
+->values(array(
+  'path' => 'admin/content/node-type/forum/fields/field_forums/remove',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:3:{i:0;s:25:"content_field_remove_form";i:1;s:5:"forum";i:2;s:12:"field_forums";}',
+  'fit' => '127',
+  'number_parts' => '7',
+  'tab_parent' => '',
+  'tab_root' => 'admin/content/node-type/forum/fields/field_forums/remove',
+  'title' => 'Remove field',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'type' => '4',
+  'block_callback' => '',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'file' => 'sites/all/modules/cck/includes/content.admin.inc',
+))
 ->values(array(
   'path' => 'admin/content/node-type/page',
   'load_functions' => '',
@@ -15755,6 +15905,23 @@
   'tnid' => '0',
   'translate' => '0',
 ))
+->values(array(
+  'nid' => '20',
+  'vid' => '23',
+  'type' => 'forum',
+  'language' => '',
+  'title' => 'Earth topic 1',
+  'uid' => '1',
+  'status' => '1',
+  'created' => '1679803825',
+  'changed' => '1679803825',
+  'comment' => '2',
+  'promote' => '0',
+  'moderate' => '0',
+  'sticky' => '0',
+  'tnid' => '0',
+  'translate' => '0',
+))
 ->execute();
 $connection->schema()->createTable('node_access', array(
   'fields' => array(
@@ -15896,6 +16063,13 @@
   'last_comment_uid' => '1',
   'comment_count' => '3',
 ))
+->values(array(
+  'nid' => '20',
+  'last_comment_timestamp' => '1679803825',
+  'last_comment_name' => NULL,
+  'last_comment_uid' => '1',
+  'comment_count' => '0',
+))
 ->execute();
 $connection->schema()->createTable('node_counter', array(
   'fields' => array(
@@ -16031,6 +16205,17 @@
   'timestamp' => '1501955771',
   'format' => '1',
 ))
+->values(array(
+  'nid' => '20',
+  'vid' => '23',
+  'uid' => '1',
+  'title' => 'Earth topic 1',
+  'body' => 'Foo',
+  'teaser' => 'Foo',
+  'log' => '',
+  'timestamp' => '1679803825',
+  'format' => '1',
+))
 ->execute();
 $connection->schema()->createTable('node_type', array(
   'fields' => array(
@@ -17268,10 +17453,10 @@
   'name' => 'text',
   'type' => 'module',
   'owner' => '',
-  'status' => '0',
+  'status' => '1',
   'throttle' => '0',
   'bootstrap' => '0',
-  'schema_version' => '-1',
+  'schema_version' => '6003',
   'weight' => '0',
   'info' => 'a:8:{s:4:"name";s:4:"Text";s:11:"description";s:32:"Defines simple text field types.";s:12:"dependencies";a:1:{i:0;s:7:"content";}s:7:"package";s:3:"CCK";s:4:"core";s:3:"6.x";s:10:"dependents";a:0:{}s:7:"version";N;s:3:"php";s:5:"4.3.5";}',
 ))
@@ -17688,6 +17873,34 @@
   'vid' => '7',
   'name' => 'General discussion',
   'description' => '',
+  'weight' => '2',
+))
+->values(array(
+  'tid' => '9',
+  'vid' => '7',
+  'name' => 'Earth',
+  'description' => '',
+  'weight' => '0',
+))
+->values(array(
+  'tid' => '10',
+  'vid' => '7',
+  'name' => 'Birds',
+  'description' => '',
+  'weight' => '0',
+))
+->values(array(
+  'tid' => '11',
+  'vid' => '8',
+  'name' => 'Oak',
+  'description' => '',
+  'weight' => '0',
+))
+->values(array(
+  'tid' => '12',
+  'vid' => '8',
+  'name' => 'Ash',
+  'description' => '',
   'weight' => '0',
 ))
 ->execute();
@@ -17724,6 +17937,22 @@
   'tid' => '8',
   'parent' => '0',
 ))
+->values(array(
+  'tid' => '9',
+  'parent' => '0',
+))
+->values(array(
+  'tid' => '10',
+  'parent' => '9',
+))
+->values(array(
+  'tid' => '11',
+  'parent' => '0',
+))
+->values(array(
+  'tid' => '12',
+  'parent' => '0',
+))
 ->execute();
 $connection->schema()->createTable('term_node', array(
   'fields' => array(
@@ -17767,6 +17996,16 @@
   'vid' => '22',
   'tid' => '8',
 ))
+->values(array(
+  'nid' => '20',
+  'vid' => '23',
+  'tid' => '10',
+))
+->values(array(
+  'nid' => '20',
+  'vid' => '23',
+  'tid' => '12',
+))
 ->execute();
 $connection->schema()->createTable('term_relation', array(
   'fields' => array(
@@ -18128,8 +18367,8 @@
   'signature' => '',
   'signature_format' => '0',
   'created' => '0',
-  'access' => '1679873221',
-  'login' => '1679872166',
+  'access' => '1687738850',
+  'login' => '1687738850',
   'status' => '1',
   'timezone' => NULL,
   'language' => '',
@@ -18471,6 +18710,10 @@
   'name' => 'content_extra_weights_employee',
   'value' => 'a:11:{s:5:"title";s:2:"-5";s:10:"body_field";s:1:"0";s:20:"revision_information";s:2:"20";s:6:"author";s:2:"20";s:7:"options";s:2:"25";s:16:"comment_settings";s:2:"30";s:8:"language";s:1:"0";s:11:"translation";s:2:"30";s:4:"menu";s:2:"-2";s:4:"book";s:2:"10";s:4:"path";s:2:"30";}',
 ))
+->values(array(
+  'name' => 'content_extra_weights_forum',
+  'value' => 'a:9:{s:5:"title";s:2:"-5";s:10:"body_field";s:1:"0";s:20:"revision_information";s:2:"20";s:6:"author";s:2:"20";s:7:"options";s:2:"25";s:16:"comment_settings";s:2:"30";s:4:"menu";s:2:"-2";s:8:"taxonomy";s:2:"-3";s:4:"path";s:2:"30";}',
+))
 ->values(array(
   'name' => 'content_extra_weights_story',
   'value' => 'a:9:{s:5:"title";s:2:"-5";s:10:"body_field";s:2:"-2";s:20:"revision_information";s:2:"19";s:6:"author";s:2:"18";s:7:"options";s:2:"20";s:16:"comment_settings";s:2:"22";s:4:"menu";s:2:"-3";s:8:"taxonomy";s:2:"-4";s:11:"attachments";s:2:"21";}',
@@ -18497,7 +18740,7 @@
 ))
 ->values(array(
   'name' => 'css_js_query_string',
-  'value' => 's:20:"sEOgby8SAkMTxRZndiw7";',
+  'value' => 's:20:"mEOgby8SAkMTxRZndiw7";',
 ))
 ->values(array(
   'name' => 'date:story:4:field_test_datestamp_fromto',
@@ -18887,6 +19130,10 @@
   'name' => 'forum_block_num_1',
   'value' => 's:1:"4";',
 ))
+->values(array(
+  'name' => 'forum_containers',
+  'value' => 'a:1:{i:0;s:1:"9";}',
+))
 ->values(array(
   'name' => 'forum_hot_topic',
   'value' => 's:2:"15";',
@@ -19387,6 +19634,19 @@
   'description' => '',
   'help' => '',
   'relations' => '1',
+  'hierarchy' => '1',
+  'multiple' => '0',
+  'required' => '0',
+  'tags' => '0',
+  'module' => 'taxonomy',
+  'weight' => '0',
+))
+->values(array(
+  'vid' => '8',
+  'name' => 'Trees',
+  'description' => 'A list of trees.',
+  'help' => '',
+  'relations' => '1',
   'hierarchy' => '0',
   'multiple' => '0',
   'required' => '0',
@@ -19394,6 +19654,19 @@
   'module' => 'taxonomy',
   'weight' => '0',
 ))
+->values(array(
+  'vid' => '9',
+  'name' => 'FreeTags',
+  'description' => '',
+  'help' => '',
+  'relations' => '1',
+  'hierarchy' => '0',
+  'multiple' => '0',
+  'required' => '0',
+  'tags' => '1',
+  'module' => 'taxonomy',
+  'weight' => '0',
+))
 ->execute();
 $connection->schema()->createTable('vocabulary_node_types', array(
   'fields' => array(
@@ -19427,6 +19700,14 @@
   'vid' => '7',
   'type' => 'forum',
 ))
+->values(array(
+  'vid' => '8',
+  'type' => 'forum',
+))
+->values(array(
+  'vid' => '9',
+  'type' => 'forum',
+))
 ->execute();
 $connection->schema()->createTable('watchdog', array(
   'fields' => array(
diff --git a/core/modules/forum/tests/fixtures/drupal7.php b/core/modules/forum/tests/fixtures/drupal7.php
index 5715b03824bbf9a5b039d50d4cffaa256fcc84c8..95f92005c0b125e6215b8b082dc664511dbd63d8 100644
--- a/core/modules/forum/tests/fixtures/drupal7.php
+++ b/core/modules/forum/tests/fixtures/drupal7.php
@@ -1353,6 +1353,49 @@
   'mysql_character_set' => 'utf8',
 ));
 
+$connection->schema()->createTable('cache_variable', array(
+  'fields' => array(
+    'cid' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'data' => array(
+      'type' => 'blob',
+      'not null' => FALSE,
+      'size' => 'big',
+    ),
+    'expire' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'created' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'serialized' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'small',
+      'default' => '0',
+    ),
+  ),
+  'primary key' => array(
+    'cid',
+  ),
+  'indexes' => array(
+    'expire' => array(
+      'expire',
+    ),
+  ),
+  'mysql_character_set' => 'utf8',
+));
+
 $connection->schema()->createTable('comment', array(
   'fields' => array(
     'cid' => array(
@@ -1770,6 +1813,153 @@
   'locked' => '0',
 ))
 ->execute();
+$connection->schema()->createTable('entity_translation', array(
+  'fields' => array(
+    'entity_type' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '128',
+      'default' => '',
+    ),
+    'entity_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'revision_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'language' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '32',
+      'default' => '',
+    ),
+    'source' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '32',
+      'default' => '',
+    ),
+    'uid' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'status' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '1',
+    ),
+    'translate' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'created' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'changed' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+  ),
+  'primary key' => array(
+    'entity_type',
+    'entity_id',
+    'language',
+  ),
+  'mysql_character_set' => 'utf8',
+));
+
+$connection->schema()->createTable('entity_translation_revision', array(
+  'fields' => array(
+    'entity_type' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '128',
+      'default' => '',
+    ),
+    'entity_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'revision_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'language' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '32',
+      'default' => '',
+    ),
+    'source' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '32',
+      'default' => '',
+    ),
+    'uid' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'status' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '1',
+    ),
+    'translate' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'created' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'changed' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+  ),
+  'primary key' => array(
+    'entity_type',
+    'revision_id',
+    'language',
+  ),
+  'indexes' => array(
+    'revision_id' => array(
+      'revision_id',
+    ),
+  ),
+  'mysql_character_set' => 'utf8',
+));
+
 $connection->schema()->createTable('field_config', array(
   'fields' => array(
     'id' => array(
@@ -1908,7 +2098,7 @@
   'storage_module' => 'field_sql_storage',
   'storage_active' => '1',
   'locked' => '0',
-  'data' => 'a:7:{s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:4:"tags";s:6:"parent";i:0;}}s:21:"options_list_callback";N;s:23:"entity_translation_sync";b:0;}s:12:"entity_types";a:0:{}s:12:"translatable";s:1:"0";s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:21:"field_data_field_tags";a:1:{s:3:"tid";s:14:"field_tags_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:25:"field_revision_field_tags";a:1:{s:3:"tid";s:14:"field_tags_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:1:"3";}',
+  'data' => 'a:7:{s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:4:"tags";s:6:"parent";i:0;}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:12:"entity_types";a:0:{}s:12:"translatable";s:1:"0";s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:21:"field_data_field_tags";a:1:{s:3:"tid";s:14:"field_tags_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:25:"field_revision_field_tags";a:1:{s:3:"tid";s:14:"field_tags_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:1:"3";}',
   'cardinality' => '-1',
   'translatable' => '0',
   'deleted' => '0',
@@ -1938,7 +2128,7 @@
   'storage_module' => 'field_sql_storage',
   'storage_active' => '1',
   'locked' => '0',
-  'data' => 'a:7:{s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:19:"sujet_de_discussion";s:6:"parent";i:0;}}s:21:"options_list_callback";N;s:23:"entity_translation_sync";b:0;}s:12:"entity_types";a:0:{}s:12:"translatable";s:1:"0";s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:26:"field_data_taxonomy_forums";a:1:{s:3:"tid";s:19:"taxonomy_forums_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:30:"field_revision_taxonomy_forums";a:1:{s:3:"tid";s:19:"taxonomy_forums_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:1:"5";}',
+  'data' => 'a:7:{s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:19:"sujet_de_discussion";s:6:"parent";i:0;}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:12:"entity_types";a:0:{}s:12:"translatable";s:1:"0";s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:26:"field_data_taxonomy_forums";a:1:{s:3:"tid";s:19:"taxonomy_forums_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:30:"field_revision_taxonomy_forums";a:1:{s:3:"tid";s:19:"taxonomy_forums_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:1:"5";}',
   'cardinality' => '1',
   'translatable' => '0',
   'deleted' => '0',
@@ -2095,15 +2285,6 @@
   'data' => 'a:6:{s:5:"label";s:7:"Comment";s:8:"settings";a:2:{s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:8:"required";b:1;s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:6:"weight";i:0;s:8:"settings";a:0:{}s:6:"module";s:4:"text";}}s:6:"widget";a:4:{s:4:"type";s:13:"text_textarea";s:8:"settings";a:1:{s:4:"rows";i:5;}s:6:"weight";i:0;s:6:"module";s:4:"text";}s:11:"description";s:0:"";}',
   'deleted' => '0',
 ))
-->values(array(
-  'id' => '9',
-  'field_id' => '1',
-  'field_name' => 'comment_body',
-  'entity_type' => 'comment',
-  'bundle' => 'comment_node_book',
-  'data' => 'a:6:{s:5:"label";s:7:"Comment";s:8:"settings";a:2:{s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:8:"required";b:1;s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:6:"weight";i:0;s:8:"settings";a:0:{}s:6:"module";s:4:"text";}}s:6:"widget";a:4:{s:4:"type";s:13:"text_textarea";s:8:"settings";a:1:{s:4:"rows";i:5;}s:6:"weight";i:0;s:6:"module";s:4:"text";}s:11:"description";s:0:"";}',
-  'deleted' => '1',
-))
 ->values(array(
   'id' => '11',
   'field_id' => '5',
@@ -2149,6 +2330,42 @@
   'data' => 'a:6:{s:5:"label";s:5:"event";s:6:"widget";a:5:{s:6:"weight";s:1:"2";s:4:"type";s:9:"date_text";s:6:"module";s:4:"date";s:6:"active";i:1;s:8:"settings";a:7:{s:12:"input_format";s:13:"m/d/Y - H:i:s";s:19:"input_format_custom";s:0:"";s:10:"year_range";s:5:"-3:+3";s:9:"increment";i:1;s:14:"label_position";s:5:"above";s:10:"text_parts";a:0:{}s:11:"no_fieldset";i:0;}}s:8:"settings";a:6:{s:13:"default_value";s:3:"now";s:18:"default_value_code";s:0:"";s:14:"default_value2";s:4:"same";s:19:"default_value_code2";s:0:"";s:18:"user_register_form";b:0;s:23:"entity_translation_sync";b:0;}s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:5:"above";s:4:"type";s:12:"date_default";s:8:"settings";a:6:{s:11:"format_type";s:4:"long";s:15:"multiple_number";s:0:"";s:13:"multiple_from";s:0:"";s:11:"multiple_to";s:0:"";s:6:"fromto";s:4:"both";s:19:"show_remaining_days";b:0;}s:6:"module";s:4:"date";s:6:"weight";i:12;}}s:8:"required";i:0;s:11:"description";s:0:"";}',
   'deleted' => '0',
 ))
+->values(array(
+  'id' => '95',
+  'field_id' => '1',
+  'field_name' => 'comment_body',
+  'entity_type' => 'comment',
+  'bundle' => 'comment_node_et',
+  'data' => 'a:6:{s:5:"label";s:7:"Comment";s:8:"settings";a:2:{s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:8:"required";b:1;s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:6:"weight";i:0;s:8:"settings";a:0:{}s:6:"module";s:4:"text";}}s:6:"widget";a:4:{s:4:"type";s:13:"text_textarea";s:8:"settings";a:1:{s:4:"rows";i:5;}s:6:"weight";i:0;s:6:"module";s:4:"text";}s:11:"description";s:0:"";}',
+  'deleted' => '0',
+))
+->values(array(
+  'id' => '96',
+  'field_id' => '2',
+  'field_name' => 'body',
+  'entity_type' => 'node',
+  'bundle' => 'et',
+  'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}',
+  'deleted' => '0',
+))
+->values(array(
+  'id' => '97',
+  'field_id' => '1',
+  'field_name' => 'comment_body',
+  'entity_type' => 'comment',
+  'bundle' => 'comment_node_test_content_type',
+  'data' => 'a:6:{s:5:"label";s:7:"Comment";s:8:"settings";a:2:{s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:8:"required";b:1;s:7:"display";a:1:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:6:"weight";i:0;s:8:"settings";a:0:{}s:6:"module";s:4:"text";}}s:6:"widget";a:4:{s:4:"type";s:13:"text_textarea";s:8:"settings";a:1:{s:4:"rows";i:5;}s:6:"weight";i:0;s:6:"module";s:4:"text";}s:11:"description";s:0:"";}',
+  'deleted' => '0',
+))
+->values(array(
+  'id' => '98',
+  'field_id' => '2',
+  'field_name' => 'body',
+  'entity_type' => 'node',
+  'bundle' => 'test_content_type',
+  'data' => 'a:6:{s:5:"label";s:4:"Body";s:6:"widget";a:4:{s:4:"type";s:26:"text_textarea_with_summary";s:8:"settings";a:2:{s:4:"rows";i:20;s:12:"summary_rows";i:5;}s:6:"weight";i:-4;s:6:"module";s:4:"text";}s:8:"settings";a:3:{s:15:"display_summary";b:1;s:15:"text_processing";i:1;s:18:"user_register_form";b:0;}s:7:"display";a:2:{s:7:"default";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:12:"text_default";s:8:"settings";a:0:{}s:6:"module";s:4:"text";s:6:"weight";i:0;}s:6:"teaser";a:5:{s:5:"label";s:6:"hidden";s:4:"type";s:23:"text_summary_or_trimmed";s:8:"settings";a:1:{s:11:"trim_length";i:600;}s:6:"module";s:4:"text";s:6:"weight";i:0;}}s:8:"required";b:0;s:11:"description";s:0:"";}',
+  'deleted' => '0',
+))
 ->execute();
 $connection->schema()->createTable('field_data_body', array(
   'fields' => array(
@@ -4158,47 +4375,228 @@
   'mysql_character_set' => 'utf8',
 ));
 
-$connection->schema()->createTable('image_effects', array(
+$connection->schema()->createTable('i18n_string', array(
   'fields' => array(
-    'ieid' => array(
-      'type' => 'serial',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'unsigned' => TRUE,
-    ),
-    'isid' => array(
+    'lid' => array(
       'type' => 'int',
       'not null' => TRUE,
       'size' => 'normal',
       'default' => '0',
-      'unsigned' => TRUE,
     ),
-    'weight' => array(
-      'type' => 'int',
+    'textgroup' => array(
+      'type' => 'varchar',
       'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
+      'length' => '50',
+      'default' => 'default',
     ),
-    'name' => array(
+    'context' => array(
       'type' => 'varchar',
       'not null' => TRUE,
       'length' => '255',
+      'default' => '',
     ),
-    'data' => array(
-      'type' => 'blob',
+    'objectid' => array(
+      'type' => 'varchar',
       'not null' => TRUE,
-      'size' => 'normal',
+      'length' => '255',
+      'default' => '',
     ),
-  ),
-  'primary key' => array(
-    'ieid',
-  ),
-  'mysql_character_set' => 'utf8',
-));
-
-$connection->insert('image_effects')
-->fields(array(
-  'ieid',
+    'type' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'property' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'objectindex' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'big',
+      'default' => '0',
+    ),
+    'format' => array(
+      'type' => 'varchar',
+      'not null' => FALSE,
+      'length' => '255',
+    ),
+  ),
+  'primary key' => array(
+    'lid',
+  ),
+  'indexes' => array(
+    'group_context' => array(
+      'textgroup',
+      array(
+        'context',
+        '50',
+      ),
+    ),
+  ),
+  'mysql_character_set' => 'utf8',
+));
+
+$connection->insert('i18n_string')
+->fields(array(
+  'lid',
+  'textgroup',
+  'context',
+  'objectid',
+  'type',
+  'property',
+  'objectindex',
+  'format',
+))
+->values(array(
+  'lid' => '272',
+  'textgroup' => 'taxonomy',
+  'context' => 'vocabulary:2:name',
+  'objectid' => '2',
+  'type' => 'vocabulary',
+  'property' => 'name',
+  'objectindex' => '2',
+  'format' => '',
+))
+->values(array(
+  'lid' => '273',
+  'textgroup' => 'taxonomy',
+  'context' => 'vocabulary:2:description',
+  'objectid' => '2',
+  'type' => 'vocabulary',
+  'property' => 'description',
+  'objectindex' => '2',
+  'format' => '',
+))
+->values(array(
+  'lid' => '274',
+  'textgroup' => 'taxonomy',
+  'context' => 'vocabulary:1:name',
+  'objectid' => '1',
+  'type' => 'vocabulary',
+  'property' => 'name',
+  'objectindex' => '1',
+  'format' => '',
+))
+->values(array(
+  'lid' => '275',
+  'textgroup' => 'taxonomy',
+  'context' => 'vocabulary:1:description',
+  'objectid' => '1',
+  'type' => 'vocabulary',
+  'property' => 'description',
+  'objectindex' => '1',
+  'format' => '',
+))
+->execute();
+$connection->schema()->createTable('i18n_translation_set', array(
+  'fields' => array(
+    'tsid' => array(
+      'type' => 'serial',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'title' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'type' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '32',
+      'default' => '',
+    ),
+    'bundle' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '128',
+      'default' => '',
+    ),
+    'master_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+      'unsigned' => TRUE,
+    ),
+    'status' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '1',
+    ),
+    'created' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'changed' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+  ),
+  'primary key' => array(
+    'tsid',
+  ),
+  'indexes' => array(
+    'entity_bundle' => array(
+      'type',
+      'bundle',
+    ),
+  ),
+  'mysql_character_set' => 'utf8',
+));
+
+$connection->schema()->createTable('image_effects', array(
+  'fields' => array(
+    'ieid' => array(
+      'type' => 'serial',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'isid' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+      'unsigned' => TRUE,
+    ),
+    'weight' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'name' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+    ),
+    'data' => array(
+      'type' => 'blob',
+      'not null' => TRUE,
+      'size' => 'normal',
+    ),
+  ),
+  'primary key' => array(
+    'ieid',
+  ),
+  'mysql_character_set' => 'utf8',
+));
+
+$connection->insert('image_effects')
+->fields(array(
+  'ieid',
   'isid',
   'weight',
   'name',
@@ -4463,6 +4861,15 @@
   'primary key' => array(
     'lid',
   ),
+  'indexes' => array(
+    'textgroup_context' => array(
+      'textgroup',
+      array(
+        'context',
+        '50',
+      ),
+    ),
+  ),
   'mysql_character_set' => 'utf8',
 ));
 
@@ -5995,6 +6402,38 @@
   'context' => '',
   'version' => '7.92',
 ))
+->values(array(
+  'lid' => '272',
+  'location' => 'taxonomy:vocabulary:2:name',
+  'textgroup' => 'taxonomy',
+  'source' => 'Subject of discussion',
+  'context' => 'vocabulary:2:name',
+  'version' => '1',
+))
+->values(array(
+  'lid' => '273',
+  'location' => 'taxonomy:vocabulary:2:description',
+  'textgroup' => 'taxonomy',
+  'source' => 'Forum navigation vocabulary',
+  'context' => 'vocabulary:2:description',
+  'version' => '1',
+))
+->values(array(
+  'lid' => '274',
+  'location' => 'taxonomy:vocabulary:1:name',
+  'textgroup' => 'taxonomy',
+  'source' => 'Tags',
+  'context' => 'vocabulary:1:name',
+  'version' => '1',
+))
+->values(array(
+  'lid' => '275',
+  'location' => 'taxonomy:vocabulary:1:description',
+  'textgroup' => 'taxonomy',
+  'source' => 'Use tags to group articles on similar topics into categories.',
+  'context' => 'vocabulary:1:description',
+  'version' => '1',
+))
 ->execute();
 $connection->schema()->createTable('locales_target', array(
   'fields' => array(
@@ -6027,6 +6466,12 @@
       'size' => 'normal',
       'default' => '0',
     ),
+    'i18n_status' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
   ),
   'primary key' => array(
     'lid',
@@ -6043,6 +6488,7 @@
   'language',
   'plid',
   'plural',
+  'i18n_status',
 ))
 ->values(array(
   'lid' => '163',
@@ -6050,6 +6496,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '678',
@@ -6057,6 +6504,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '684',
@@ -6064,6 +6512,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '687',
@@ -6071,6 +6520,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '688',
@@ -6078,6 +6528,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '758',
@@ -6085,6 +6536,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '761',
@@ -6092,6 +6544,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '761',
@@ -6099,6 +6552,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '762',
@@ -6106,6 +6560,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '762',
@@ -6113,6 +6568,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '763',
@@ -6120,6 +6576,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '763',
@@ -6127,6 +6584,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '764',
@@ -6134,6 +6592,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '764',
@@ -6141,6 +6600,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '765',
@@ -6148,6 +6608,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '765',
@@ -6155,6 +6616,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '766',
@@ -6162,6 +6624,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '766',
@@ -6169,6 +6632,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '767',
@@ -6176,6 +6640,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '767',
@@ -6183,6 +6648,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '768',
@@ -6190,6 +6656,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '768',
@@ -6197,6 +6664,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '797',
@@ -6204,6 +6672,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '798',
@@ -6211,6 +6680,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '800',
@@ -6218,6 +6688,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '800',
@@ -6225,6 +6696,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '801',
@@ -6232,6 +6704,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '801',
@@ -6239,6 +6712,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '802',
@@ -6246,6 +6720,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '803',
@@ -6253,6 +6728,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '804',
@@ -6260,6 +6736,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '805',
@@ -6267,6 +6744,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '805',
@@ -6274,6 +6752,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '806',
@@ -6281,6 +6760,7 @@
   'language' => 'fr',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->values(array(
   'lid' => '806',
@@ -6288,6 +6768,7 @@
   'language' => 'is',
   'plid' => '0',
   'plural' => '0',
+  'i18n_status' => '0',
 ))
 ->execute();
 $connection->schema()->createTable('menu_custom', array(
@@ -11275,9 +11756,9 @@
   'module' => 'system',
   'hidden' => '-1',
   'external' => '0',
-  'has_children' => '0',
+  'has_children' => '1',
   'expanded' => '0',
-  'weight' => '2',
+  'weight' => '1',
   'depth' => '2',
   'customized' => '0',
   'p1' => '5',
@@ -13019,95 +13500,581 @@
   'p9' => '0',
   'updated' => '0',
 ))
-->execute();
-$connection->schema()->createTable('menu_router', array(
-  'fields' => array(
-    'path' => array(
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ),
-    'load_functions' => array(
-      'type' => 'blob',
-      'not null' => TRUE,
-      'size' => 'normal',
-    ),
-    'to_arg_functions' => array(
-      'type' => 'blob',
-      'not null' => TRUE,
-      'size' => 'normal',
-    ),
-    'access_callback' => array(
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ),
-    'access_arguments' => array(
-      'type' => 'blob',
-      'not null' => FALSE,
-      'size' => 'normal',
-    ),
-    'page_callback' => array(
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ),
-    'page_arguments' => array(
-      'type' => 'blob',
-      'not null' => FALSE,
-      'size' => 'normal',
-    ),
-    'delivery_callback' => array(
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ),
-    'fit' => array(
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ),
-    'number_parts' => array(
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ),
-    'context' => array(
-      'type' => 'int',
-      'not null' => TRUE,
-      'size' => 'normal',
-      'default' => '0',
-    ),
-    'tab_parent' => array(
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ),
-    'tab_root' => array(
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ),
-    'title' => array(
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ),
-    'title_callback' => array(
-      'type' => 'varchar',
-      'not null' => TRUE,
-      'length' => '255',
-      'default' => '',
-    ),
+->values(array(
+  'menu_name' => 'navigation',
+  'mlid' => '966',
+  'plid' => '6',
+  'link_path' => 'node/add/et',
+  'router_path' => 'node/add/et',
+  'link_title' => 'et',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '0',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '2',
+  'customized' => '0',
+  'p1' => '6',
+  'p2' => '966',
+  'p3' => '0',
+  'p4' => '0',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'navigation',
+  'mlid' => '967',
+  'plid' => '6',
+  'link_path' => 'node/add/test-content-type',
+  'router_path' => 'node/add/test-content-type',
+  'link_title' => 'test_content_type',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '0',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '2',
+  'customized' => '0',
+  'p1' => '6',
+  'p2' => '967',
+  'p3' => '0',
+  'p4' => '0',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '968',
+  'plid' => '48',
+  'link_path' => 'admin/config/regional/i18n',
+  'router_path' => 'admin/config/regional/i18n',
+  'link_title' => 'Multilingual settings',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:69:"Configure extended options for multilingual content and translations.";}}',
+  'module' => 'system',
+  'hidden' => '0',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '10',
+  'depth' => '4',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '8',
+  'p3' => '48',
+  'p4' => '968',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '969',
+  'plid' => '968',
+  'link_path' => 'admin/config/regional/i18n/configure',
+  'router_path' => 'admin/config/regional/i18n/configure',
+  'link_title' => 'Multilingual system',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:69:"Configure extended options for multilingual content and translations.";}}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '5',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '8',
+  'p3' => '48',
+  'p4' => '968',
+  'p5' => '969',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '970',
+  'plid' => '48',
+  'link_path' => 'admin/config/regional/entity_translation',
+  'router_path' => 'admin/config/regional/entity_translation',
+  'link_title' => 'Entity translation',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:83:"Configure which entities can be translated and enable or disable language fallback.";}}',
+  'module' => 'system',
+  'hidden' => '0',
+  'external' => '0',
+  'has_children' => '1',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '4',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '8',
+  'p3' => '48',
+  'p4' => '970',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'navigation',
+  'mlid' => '971',
+  'plid' => '39',
+  'link_path' => 'node/%/edit/%',
+  'router_path' => 'node/%/edit/%',
+  'link_title' => 'Edit',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '3',
+  'customized' => '0',
+  'p1' => '5',
+  'p2' => '39',
+  'p3' => '971',
+  'p4' => '0',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'navigation',
+  'mlid' => '972',
+  'plid' => '365',
+  'link_path' => 'node/%/translate/delete/%',
+  'router_path' => 'node/%/translate/delete/%',
+  'link_title' => 'Delete',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '0',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '3',
+  'customized' => '0',
+  'p1' => '5',
+  'p2' => '365',
+  'p3' => '972',
+  'p4' => '0',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'navigation',
+  'mlid' => '973',
+  'plid' => '39',
+  'link_path' => 'node/%/edit/add/%/%',
+  'router_path' => 'node/%/edit/add/%/%',
+  'link_title' => 'Edit',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '3',
+  'customized' => '0',
+  'p1' => '5',
+  'p2' => '39',
+  'p3' => '973',
+  'p4' => '0',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '974',
+  'plid' => '970',
+  'link_path' => 'admin/config/regional/entity_translation/translatable/%',
+  'router_path' => 'admin/config/regional/entity_translation/translatable/%',
+  'link_title' => 'Confirm change in translatability.',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:53:"Confirmation page for changing field translatability.";}}',
+  'module' => 'system',
+  'hidden' => '0',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '5',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '8',
+  'p3' => '48',
+  'p4' => '970',
+  'p5' => '974',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '975',
+  'plid' => '968',
+  'link_path' => 'admin/config/regional/i18n/strings',
+  'router_path' => 'admin/config/regional/i18n/strings',
+  'link_title' => 'Strings',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:33:"Options for user defined strings.";}}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '20',
+  'depth' => '5',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '8',
+  'p3' => '48',
+  'p4' => '968',
+  'p5' => '975',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '976',
+  'plid' => '413',
+  'link_path' => 'admin/config/regional/translate/i18n_string',
+  'router_path' => 'admin/config/regional/translate/i18n_string',
+  'link_title' => 'Strings',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:29:"Refresh user defined strings.";}}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '20',
+  'depth' => '5',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '8',
+  'p3' => '48',
+  'p4' => '413',
+  'p5' => '976',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'navigation',
+  'mlid' => '977',
+  'plid' => '176',
+  'link_path' => 'taxonomy/term/%/translate',
+  'router_path' => 'taxonomy/term/%/translate',
+  'link_title' => 'Translate',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '10',
+  'depth' => '2',
+  'customized' => '0',
+  'p1' => '176',
+  'p2' => '977',
+  'p3' => '0',
+  'p4' => '0',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '978',
+  'plid' => '48',
+  'link_path' => 'admin/config/regional/i18n_translation',
+  'router_path' => 'admin/config/regional/i18n_translation',
+  'link_title' => 'Translation sets',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:26:"Translation sets overview.";}}',
+  'module' => 'system',
+  'hidden' => '0',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '10',
+  'depth' => '4',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '8',
+  'p3' => '48',
+  'p4' => '978',
+  'p5' => '0',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '979',
+  'plid' => '212',
+  'link_path' => 'admin/structure/taxonomy/%/translate',
+  'router_path' => 'admin/structure/taxonomy/%/translate',
+  'link_title' => 'Translate',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '10',
+  'depth' => '5',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '20',
+  'p3' => '180',
+  'p4' => '212',
+  'p5' => '979',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '980',
+  'plid' => '978',
+  'link_path' => 'admin/config/regional/i18n_translation/configure',
+  'router_path' => 'admin/config/regional/i18n_translation/configure',
+  'link_title' => 'Translation sets',
+  'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:38:"Overview of existing translation sets.";}}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '5',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '8',
+  'p3' => '48',
+  'p4' => '978',
+  'p5' => '980',
+  'p6' => '0',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '981',
+  'plid' => '226',
+  'link_path' => 'admin/structure/taxonomy/%/list/list',
+  'router_path' => 'admin/structure/taxonomy/%/list/list',
+  'link_title' => 'Terms',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '-20',
+  'depth' => '6',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '20',
+  'p3' => '180',
+  'p4' => '212',
+  'p5' => '226',
+  'p6' => '981',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '982',
+  'plid' => '226',
+  'link_path' => 'admin/structure/taxonomy/%/list/sets',
+  'router_path' => 'admin/structure/taxonomy/%/list/sets',
+  'link_title' => 'Translation sets',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '6',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '20',
+  'p3' => '180',
+  'p4' => '212',
+  'p5' => '226',
+  'p6' => '982',
+  'p7' => '0',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->values(array(
+  'menu_name' => 'management',
+  'mlid' => '983',
+  'plid' => '982',
+  'link_path' => 'admin/structure/taxonomy/%/list/sets/add',
+  'router_path' => 'admin/structure/taxonomy/%/list/sets/add',
+  'link_title' => 'Create new translation',
+  'options' => 'a:0:{}',
+  'module' => 'system',
+  'hidden' => '-1',
+  'external' => '0',
+  'has_children' => '0',
+  'expanded' => '0',
+  'weight' => '0',
+  'depth' => '7',
+  'customized' => '0',
+  'p1' => '1',
+  'p2' => '20',
+  'p3' => '180',
+  'p4' => '212',
+  'p5' => '226',
+  'p6' => '982',
+  'p7' => '983',
+  'p8' => '0',
+  'p9' => '0',
+  'updated' => '0',
+))
+->execute();
+$connection->schema()->createTable('menu_router', array(
+  'fields' => array(
+    'path' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'load_functions' => array(
+      'type' => 'blob',
+      'not null' => TRUE,
+      'size' => 'normal',
+    ),
+    'to_arg_functions' => array(
+      'type' => 'blob',
+      'not null' => TRUE,
+      'size' => 'normal',
+    ),
+    'access_callback' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'access_arguments' => array(
+      'type' => 'blob',
+      'not null' => FALSE,
+      'size' => 'normal',
+    ),
+    'page_callback' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'page_arguments' => array(
+      'type' => 'blob',
+      'not null' => FALSE,
+      'size' => 'normal',
+    ),
+    'delivery_callback' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'fit' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'number_parts' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'context' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+    ),
+    'tab_parent' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'tab_root' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'title' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
+    'title_callback' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '255',
+      'default' => '',
+    ),
     'title_arguments' => array(
       'type' => 'varchar',
       'not null' => TRUE,
@@ -14787,6 +15754,181 @@
   'weight' => '-10',
   'include_file' => 'modules/system/system.admin.inc',
 ))
+->values(array(
+  'path' => 'admin/config/regional/entity_translation',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:29:"administer entity translation";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:1:{i:0;s:29:"entity_translation_admin_form";}',
+  'delivery_callback' => '',
+  'fit' => '15',
+  'number_parts' => '4',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'admin/config/regional/entity_translation',
+  'title' => 'Entity translation',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '6',
+  'description' => 'Configure which entities can be translated and enable or disable language fallback.',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/entity_translation/entity_translation.admin.inc',
+))
+->values(array(
+  'path' => 'admin/config/regional/entity_translation/translatable/%',
+  'load_functions' => 'a:1:{i:5;N;}',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:28:"toggle field translatability";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:2:{i:0;s:36:"entity_translation_translatable_form";i:1;i:5;}',
+  'delivery_callback' => '',
+  'fit' => '62',
+  'number_parts' => '6',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'admin/config/regional/entity_translation/translatable/%',
+  'title' => 'Confirm change in translatability.',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '6',
+  'description' => 'Confirmation page for changing field translatability.',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/entity_translation/entity_translation.admin.inc',
+))
+->values(array(
+  'path' => 'admin/config/regional/i18n',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:2:{i:0;s:20:"variable_module_form";i:1;s:4:"i18n";}',
+  'delivery_callback' => '',
+  'fit' => '15',
+  'number_parts' => '4',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'admin/config/regional/i18n',
+  'title' => 'Multilingual settings',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '6',
+  'description' => 'Configure extended options for multilingual content and translations.',
+  'position' => '',
+  'weight' => '10',
+  'include_file' => '',
+))
+->values(array(
+  'path' => 'admin/config/regional/i18n/configure',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:2:{i:0;s:20:"variable_module_form";i:1;s:4:"i18n";}',
+  'delivery_callback' => '',
+  'fit' => '31',
+  'number_parts' => '5',
+  'context' => '1',
+  'tab_parent' => 'admin/config/regional/i18n',
+  'tab_root' => 'admin/config/regional/i18n',
+  'title' => 'Multilingual system',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '140',
+  'description' => 'Configure extended options for multilingual content and translations.',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => '',
+))
+->values(array(
+  'path' => 'admin/config/regional/i18n/strings',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:2:{i:0;s:18:"variable_edit_form";i:1;a:3:{i:0;s:27:"i18n_string_allowed_formats";i:1;s:27:"i18n_string_source_language";i:2;s:39:"i18n_string_textgroup_class_[textgroup]";}}',
+  'delivery_callback' => '',
+  'fit' => '31',
+  'number_parts' => '5',
+  'context' => '1',
+  'tab_parent' => 'admin/config/regional/i18n',
+  'tab_root' => 'admin/config/regional/i18n',
+  'title' => 'Strings',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '132',
+  'description' => 'Options for user defined strings.',
+  'position' => '',
+  'weight' => '20',
+  'include_file' => '',
+))
+->values(array(
+  'path' => 'admin/config/regional/i18n_translation',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}',
+  'page_callback' => 'i18n_translation_admin_overview',
+  'page_arguments' => 'a:0:{}',
+  'delivery_callback' => '',
+  'fit' => '15',
+  'number_parts' => '4',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'admin/config/regional/i18n_translation',
+  'title' => 'Translation sets',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '6',
+  'description' => 'Translation sets overview.',
+  'position' => '',
+  'weight' => '10',
+  'include_file' => 'sites/all/modules/i18n/i18n_translation/i18n_translation.admin.inc',
+))
+->values(array(
+  'path' => 'admin/config/regional/i18n_translation/configure',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}',
+  'page_callback' => 'i18n_translation_admin_overview',
+  'page_arguments' => 'a:0:{}',
+  'delivery_callback' => '',
+  'fit' => '31',
+  'number_parts' => '5',
+  'context' => '1',
+  'tab_parent' => 'admin/config/regional/i18n_translation',
+  'tab_root' => 'admin/config/regional/i18n_translation',
+  'title' => 'Translation sets',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '140',
+  'description' => 'Overview of existing translation sets.',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n_translation/i18n_translation.admin.inc',
+))
 ->values(array(
   'path' => 'admin/config/regional/language',
   'load_functions' => '',
@@ -15069,7 +16211,7 @@
   'access_callback' => 'user_access',
   'access_arguments' => 'a:1:{i:0;s:19:"translate interface";}',
   'page_callback' => 'drupal_get_form',
-  'page_arguments' => 'a:2:{i:0;s:26:"locale_translate_edit_form";i:1;i:5;}',
+  'page_arguments' => 'a:2:{i:0;s:38:"i18n_string_locale_translate_edit_form";i:1;i:5;}',
   'delivery_callback' => '',
   'fit' => '62',
   'number_parts' => '6',
@@ -15085,7 +16227,7 @@
   'description' => '',
   'position' => '',
   'weight' => '0',
-  'include_file' => 'modules/locale/locale.admin.inc',
+  'include_file' => 'sites/all/modules/i18n/i18n_string/i18n_string.pages.inc',
 ))
 ->values(array(
   'path' => 'admin/config/regional/translate/export',
@@ -15107,10 +16249,35 @@
   'theme_callback' => '',
   'theme_arguments' => 'a:0:{}',
   'type' => '132',
-  'description' => '',
+  'description' => '',
+  'position' => '',
+  'weight' => '30',
+  'include_file' => 'modules/locale/locale.admin.inc',
+))
+->values(array(
+  'path' => 'admin/config/regional/translate/i18n_string',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:19:"translate interface";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:1:{i:0;s:30:"i18n_string_admin_refresh_form";}',
+  'delivery_callback' => '',
+  'fit' => '31',
+  'number_parts' => '5',
+  'context' => '1',
+  'tab_parent' => 'admin/config/regional/translate',
+  'tab_root' => 'admin/config/regional/translate',
+  'title' => 'Strings',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '132',
+  'description' => 'Refresh user defined strings.',
   'position' => '',
-  'weight' => '30',
-  'include_file' => 'modules/locale/locale.admin.inc',
+  'weight' => '20',
+  'include_file' => 'sites/all/modules/i18n/i18n_string/i18n_string.admin.inc',
 ))
 ->values(array(
   'path' => 'admin/config/regional/translate/import',
@@ -15168,7 +16335,7 @@
   'to_arg_functions' => '',
   'access_callback' => 'user_access',
   'access_arguments' => 'a:1:{i:0;s:19:"translate interface";}',
-  'page_callback' => 'locale_translate_seek_screen',
+  'page_callback' => 'i18n_string_locale_translate_seek_screen',
   'page_arguments' => 'a:0:{}',
   'delivery_callback' => '',
   'fit' => '31',
@@ -15185,7 +16352,7 @@
   'description' => '',
   'position' => '',
   'weight' => '10',
-  'include_file' => 'modules/locale/locale.admin.inc',
+  'include_file' => 'sites/all/modules/i18n/i18n_string/i18n_string.pages.inc',
 ))
 ->values(array(
   'path' => 'admin/config/search',
@@ -17637,6 +18804,181 @@
   'weight' => '-20',
   'include_file' => 'modules/taxonomy/taxonomy.admin.inc',
 ))
+->values(array(
+  'path' => 'admin/structure/taxonomy/%/list/list',
+  'load_functions' => 'a:1:{i:3;s:37:"taxonomy_vocabulary_machine_name_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:19:"administer taxonomy";}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:2:{i:0;s:23:"taxonomy_overview_terms";i:1;i:3;}',
+  'delivery_callback' => '',
+  'fit' => '59',
+  'number_parts' => '6',
+  'context' => '1',
+  'tab_parent' => 'admin/structure/taxonomy/%/list',
+  'tab_root' => 'admin/structure/taxonomy/%',
+  'title' => 'Terms',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '140',
+  'description' => '',
+  'position' => '',
+  'weight' => '-20',
+  'include_file' => 'modules/taxonomy/taxonomy.admin.inc',
+))
+->values(array(
+  'path' => 'admin/structure/taxonomy/%/list/sets',
+  'load_functions' => 'a:1:{i:3;s:37:"taxonomy_vocabulary_machine_name_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'i18n_taxonomy_vocabulary_translation_tab_sets_access',
+  'access_arguments' => 'a:1:{i:0;i:3;}',
+  'page_callback' => 'i18n_taxonomy_translation_sets_overview',
+  'page_arguments' => 'a:1:{i:0;i:3;}',
+  'delivery_callback' => '',
+  'fit' => '59',
+  'number_parts' => '6',
+  'context' => '1',
+  'tab_parent' => 'admin/structure/taxonomy/%/list',
+  'tab_root' => 'admin/structure/taxonomy/%',
+  'title' => 'Translation sets',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '132',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.admin.inc',
+))
+->values(array(
+  'path' => 'admin/structure/taxonomy/%/list/sets/add',
+  'load_functions' => 'a:1:{i:3;s:37:"taxonomy_vocabulary_machine_name_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'i18n_taxonomy_vocabulary_translation_tab_sets_access',
+  'access_arguments' => 'a:1:{i:0;i:3;}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:2:{i:0;s:35:"i18n_taxonomy_translation_term_form";i:1;i:3;}',
+  'delivery_callback' => '',
+  'fit' => '119',
+  'number_parts' => '7',
+  'context' => '1',
+  'tab_parent' => 'admin/structure/taxonomy/%/list/sets',
+  'tab_root' => 'admin/structure/taxonomy/%',
+  'title' => 'Create new translation',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '388',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.admin.inc',
+))
+->values(array(
+  'path' => 'admin/structure/taxonomy/%/list/sets/delete/%',
+  'load_functions' => 'a:2:{i:3;s:37:"taxonomy_vocabulary_machine_name_load";i:7;s:34:"i18n_taxonomy_translation_set_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'i18n_taxonomy_vocabulary_translation_tab_sets_access',
+  'access_arguments' => 'a:1:{i:0;i:3;}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:2:{i:0;s:35:"i18n_translation_set_delete_confirm";i:1;i:7;}',
+  'delivery_callback' => '',
+  'fit' => '238',
+  'number_parts' => '8',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'admin/structure/taxonomy/%/list/sets/delete/%',
+  'title' => 'Delete translation',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '0',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => '',
+))
+->values(array(
+  'path' => 'admin/structure/taxonomy/%/list/sets/edit/%',
+  'load_functions' => 'a:2:{i:3;s:37:"taxonomy_vocabulary_machine_name_load";i:7;s:34:"i18n_taxonomy_translation_set_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'i18n_taxonomy_vocabulary_translation_tab_sets_access',
+  'access_arguments' => 'a:1:{i:0;i:3;}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:3:{i:0;s:35:"i18n_taxonomy_translation_term_form";i:1;i:3;i:2;i:7;}',
+  'delivery_callback' => '',
+  'fit' => '238',
+  'number_parts' => '8',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'admin/structure/taxonomy/%/list/sets/edit/%',
+  'title' => 'Edit translation',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '0',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.admin.inc',
+))
+->values(array(
+  'path' => 'admin/structure/taxonomy/%/translate',
+  'load_functions' => 'a:1:{i:3;s:37:"taxonomy_vocabulary_machine_name_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'i18n_object_translate_access',
+  'access_arguments' => 'a:2:{i:0;s:19:"taxonomy_vocabulary";i:1;i:3;}',
+  'page_callback' => 'i18n_page_translate_localize',
+  'page_arguments' => 'a:2:{i:0;s:19:"taxonomy_vocabulary";i:1;i:3;}',
+  'delivery_callback' => '',
+  'fit' => '29',
+  'number_parts' => '5',
+  'context' => '1',
+  'tab_parent' => 'admin/structure/taxonomy/%',
+  'tab_root' => 'admin/structure/taxonomy/%',
+  'title' => 'Translate',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '132',
+  'description' => '',
+  'position' => '',
+  'weight' => '10',
+  'include_file' => 'sites/all/modules/i18n/i18n.pages.inc',
+))
+->values(array(
+  'path' => 'admin/structure/taxonomy/%/translate/%',
+  'load_functions' => 'a:2:{i:3;s:37:"taxonomy_vocabulary_machine_name_load";i:5;s:18:"i18n_language_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'i18n_object_translate_access',
+  'access_arguments' => 'a:2:{i:0;s:19:"taxonomy_vocabulary";i:1;i:3;}',
+  'page_callback' => 'i18n_page_translate_localize',
+  'page_arguments' => 'a:3:{i:0;s:19:"taxonomy_vocabulary";i:1;i:3;i:2;i:5;}',
+  'delivery_callback' => '',
+  'fit' => '58',
+  'number_parts' => '6',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'admin/structure/taxonomy/%/translate/%',
+  'title' => 'Translate',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '0',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n.pages.inc',
+))
 ->values(array(
   'path' => 'admin/structure/taxonomy/add',
   'load_functions' => '',
@@ -18587,6 +19929,31 @@
   'weight' => '0',
   'include_file' => 'modules/comment/comment.pages.inc',
 ))
+->values(array(
+  'path' => 'entity_translation/taxonomy_term/autocomplete',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:14:"access content";}',
+  'page_callback' => 'entity_translation_taxonomy_term_autocomplete',
+  'page_arguments' => 'a:0:{}',
+  'delivery_callback' => '',
+  'fit' => '7',
+  'number_parts' => '3',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'entity_translation/taxonomy_term/autocomplete',
+  'title' => 'Entity translation autocomplete',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '0',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => '',
+))
 ->values(array(
   'path' => 'file/ajax',
   'load_functions' => '',
@@ -18737,6 +20104,81 @@
   'weight' => '0',
   'include_file' => 'modules/forum/forum.pages.inc',
 ))
+->values(array(
+  'path' => 'i18n/taxonomy/autocomplete/language/%',
+  'load_functions' => 'a:1:{i:4;N;}',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:14:"access content";}',
+  'page_callback' => 'i18n_taxonomy_autocomplete_language',
+  'page_arguments' => 'a:2:{i:0;i:4;i:1;N;}',
+  'delivery_callback' => '',
+  'fit' => '30',
+  'number_parts' => '5',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'i18n/taxonomy/autocomplete/language/%',
+  'title' => 'Autocomplete taxonomy',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '0',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc',
+))
+->values(array(
+  'path' => 'i18n/taxonomy/autocomplete/vocabulary/%/%',
+  'load_functions' => 'a:2:{i:4;s:37:"taxonomy_vocabulary_machine_name_load";i:5;N;}',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:14:"access content";}',
+  'page_callback' => 'i18n_taxonomy_autocomplete_language',
+  'page_arguments' => 'a:2:{i:0;i:5;i:1;i:4;}',
+  'delivery_callback' => '',
+  'fit' => '60',
+  'number_parts' => '6',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'i18n/taxonomy/autocomplete/vocabulary/%/%',
+  'title' => 'Autocomplete taxonomy',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '0',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc',
+))
+->values(array(
+  'path' => 'i18n_string/save',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'user_access',
+  'access_arguments' => 'a:1:{i:0;s:23:"use on-page translation";}',
+  'page_callback' => 'i18n_string_l10n_client_save_string',
+  'page_arguments' => 'a:0:{}',
+  'delivery_callback' => '',
+  'fit' => '3',
+  'number_parts' => '2',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'i18n_string/save',
+  'title' => 'Save string',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '0',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n_string/i18n_string.pages.inc',
+))
 ->values(array(
   'path' => 'node',
   'load_functions' => '',
@@ -18816,10 +20258,10 @@
   'path' => 'node/%/edit',
   'load_functions' => 'a:1:{i:1;s:9:"node_load";}',
   'to_arg_functions' => '',
-  'access_callback' => 'node_access',
-  'access_arguments' => 'a:2:{i:0;s:6:"update";i:1;i:1;}',
-  'page_callback' => 'node_page_edit',
-  'page_arguments' => 'a:1:{i:0;i:1;}',
+  'access_callback' => 'entity_translation_edit_access',
+  'access_arguments' => 'a:6:{i:0;s:4:"node";i:1;i:1;i:2;b:0;i:3;a:10:{s:5:"title";s:4:"Edit";s:13:"page callback";s:14:"node_page_edit";s:14:"page arguments";a:1:{i:0;i:1;}s:15:"access callback";s:11:"node_access";s:16:"access arguments";a:2:{i:0;s:6:"update";i:1;i:1;}s:6:"weight";i:0;s:4:"type";i:132;s:7:"context";i:3;s:4:"file";s:14:"node.pages.inc";s:6:"module";s:4:"node";}i:4;s:6:"update";i:5;i:1;}',
+  'page_callback' => 'entity_translation_edit_page',
+  'page_arguments' => 'a:5:{i:0;s:4:"node";i:1;i:1;i:2;b:0;i:3;a:10:{s:5:"title";s:4:"Edit";s:13:"page callback";s:14:"node_page_edit";s:14:"page arguments";a:1:{i:0;i:1;}s:15:"access callback";s:11:"node_access";s:16:"access arguments";a:2:{i:0;s:6:"update";i:1;i:1;}s:6:"weight";i:0;s:4:"type";i:132;s:7:"context";i:3;s:4:"file";s:14:"node.pages.inc";s:6:"module";s:4:"node";}i:4;i:1;}',
   'delivery_callback' => '',
   'fit' => '5',
   'number_parts' => '3',
@@ -18837,6 +20279,56 @@
   'weight' => '0',
   'include_file' => 'modules/node/node.pages.inc',
 ))
+->values(array(
+  'path' => 'node/%/edit/%',
+  'load_functions' => 'a:2:{i:1;s:9:"node_load";i:3;s:32:"entity_translation_language_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'entity_translation_edit_access',
+  'access_arguments' => 'a:6:{i:0;s:4:"node";i:1;i:1;i:2;i:3;i:3;a:10:{s:5:"title";s:4:"Edit";s:13:"page callback";s:14:"node_page_edit";s:14:"page arguments";a:1:{i:0;i:1;}s:15:"access callback";s:11:"node_access";s:16:"access arguments";a:2:{i:0;s:6:"update";i:1;i:1;}s:6:"weight";i:0;s:4:"type";i:132;s:7:"context";i:3;s:4:"file";s:14:"node.pages.inc";s:6:"module";s:4:"node";}i:4;s:6:"update";i:5;i:1;}',
+  'page_callback' => 'entity_translation_edit_page',
+  'page_arguments' => 'a:5:{i:0;s:4:"node";i:1;i:1;i:2;i:3;i:3;a:10:{s:5:"title";s:4:"Edit";s:13:"page callback";s:14:"node_page_edit";s:14:"page arguments";a:1:{i:0;i:1;}s:15:"access callback";s:11:"node_access";s:16:"access arguments";a:2:{i:0;s:6:"update";i:1;i:1;}s:6:"weight";i:0;s:4:"type";i:132;s:7:"context";i:3;s:4:"file";s:14:"node.pages.inc";s:6:"module";s:4:"node";}i:4;i:1;}',
+  'delivery_callback' => '',
+  'fit' => '10',
+  'number_parts' => '4',
+  'context' => '3',
+  'tab_parent' => 'node/%/edit',
+  'tab_root' => 'node/%',
+  'title' => 'Edit',
+  'title_callback' => 'entity_translation_edit_title',
+  'title_arguments' => 'a:1:{i:0;i:3;}',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '140',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'modules/node/node.pages.inc',
+))
+->values(array(
+  'path' => 'node/%/edit/add/%/%',
+  'load_functions' => 'a:3:{i:1;s:9:"node_load";i:4;s:32:"entity_translation_language_load";i:5;s:32:"entity_translation_language_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'entity_translation_add_access',
+  'access_arguments' => 'a:7:{i:0;s:4:"node";i:1;i:1;i:2;i:4;i:3;i:5;i:4;a:10:{s:5:"title";s:4:"Edit";s:13:"page callback";s:14:"node_page_edit";s:14:"page arguments";a:1:{i:0;i:1;}s:15:"access callback";s:11:"node_access";s:16:"access arguments";a:2:{i:0;s:6:"update";i:1;i:1;}s:6:"weight";i:0;s:4:"type";i:132;s:7:"context";i:3;s:4:"file";s:14:"node.pages.inc";s:6:"module";s:4:"node";}i:5;s:6:"update";i:6;i:1;}',
+  'page_callback' => 'entity_translation_add_page',
+  'page_arguments' => 'a:6:{i:0;s:4:"node";i:1;i:1;i:2;i:4;i:3;i:5;i:4;a:10:{s:5:"title";s:4:"Edit";s:13:"page callback";s:14:"node_page_edit";s:14:"page arguments";a:1:{i:0;i:1;}s:15:"access callback";s:11:"node_access";s:16:"access arguments";a:2:{i:0;s:6:"update";i:1;i:1;}s:6:"weight";i:0;s:4:"type";i:132;s:7:"context";i:3;s:4:"file";s:14:"node.pages.inc";s:6:"module";s:4:"node";}i:5;i:1;}',
+  'delivery_callback' => '',
+  'fit' => '44',
+  'number_parts' => '6',
+  'context' => '3',
+  'tab_parent' => 'node/%/edit',
+  'tab_root' => 'node/%',
+  'title' => 'Edit',
+  'title_callback' => 'Add translation',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '132',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'modules/node/node.pages.inc',
+))
 ->values(array(
   'path' => 'node/%/revisions',
   'load_functions' => 'a:1:{i:1;s:9:"node_load";}',
@@ -18941,14 +20433,14 @@
   'path' => 'node/%/translate',
   'load_functions' => 'a:1:{i:1;s:9:"node_load";}',
   'to_arg_functions' => '',
-  'access_callback' => '_translation_tab_access',
-  'access_arguments' => 'a:1:{i:0;i:1;}',
-  'page_callback' => 'translation_node_overview',
-  'page_arguments' => 'a:1:{i:0;i:1;}',
+  'access_callback' => 'entity_translation_node_tab_access',
+  'access_arguments' => 'a:3:{i:0;i:1;i:1;s:23:"_translation_tab_access";i:2;i:1;}',
+  'page_callback' => 'entity_translation_overview',
+  'page_arguments' => 'a:4:{i:0;s:4:"node";i:1;i:1;i:2;a:3:{s:13:"page callback";s:25:"translation_node_overview";s:4:"file";s:21:"translation.pages.inc";s:6:"module";s:11:"translation";}i:3;i:1;}',
   'delivery_callback' => '',
   'fit' => '5',
   'number_parts' => '3',
-  'context' => '1',
+  'context' => '3',
   'tab_parent' => 'node/%',
   'tab_root' => 'node/%',
   'title' => 'Translate',
@@ -18956,11 +20448,36 @@
   'title_arguments' => '',
   'theme_callback' => '',
   'theme_arguments' => 'a:0:{}',
-  'type' => '132',
+  'type' => '132',
+  'description' => '',
+  'position' => '',
+  'weight' => '1',
+  'include_file' => 'sites/all/modules/entity_translation/entity_translation.admin.inc',
+))
+->values(array(
+  'path' => 'node/%/translate/delete/%',
+  'load_functions' => 'a:2:{i:1;s:9:"node_load";i:4;s:32:"entity_translation_language_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'entity_translation_node_tab_access',
+  'access_arguments' => 'a:1:{i:0;i:1;}',
+  'page_callback' => 'drupal_get_form',
+  'page_arguments' => 'a:4:{i:0;s:33:"entity_translation_delete_confirm";i:1;s:4:"node";i:2;i:1;i:3;i:4;}',
+  'delivery_callback' => '',
+  'fit' => '22',
+  'number_parts' => '5',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'node/%/translate/delete/%',
+  'title' => 'Delete',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '6',
   'description' => '',
   'position' => '',
-  'weight' => '2',
-  'include_file' => 'modules/translation/translation.pages.inc',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/entity_translation/entity_translation.admin.inc',
 ))
 ->values(array(
   'path' => 'node/%/view',
@@ -19037,6 +20554,31 @@
   'weight' => '0',
   'include_file' => 'modules/node/node.pages.inc',
 ))
+->values(array(
+  'path' => 'node/add/et',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'node_access',
+  'access_arguments' => 'a:2:{i:0;s:6:"create";i:1;s:2:"et";}',
+  'page_callback' => 'node_add',
+  'page_arguments' => 'a:1:{i:0;s:2:"et";}',
+  'delivery_callback' => '',
+  'fit' => '7',
+  'number_parts' => '3',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'node/add/et',
+  'title' => 'et',
+  'title_callback' => 'check_plain',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '6',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'modules/node/node.pages.inc',
+))
 ->values(array(
   'path' => 'node/add/forum',
   'load_functions' => '',
@@ -19087,6 +20629,31 @@
   'weight' => '0',
   'include_file' => 'modules/node/node.pages.inc',
 ))
+->values(array(
+  'path' => 'node/add/test-content-type',
+  'load_functions' => '',
+  'to_arg_functions' => '',
+  'access_callback' => 'node_access',
+  'access_arguments' => 'a:2:{i:0;s:6:"create";i:1;s:17:"test_content_type";}',
+  'page_callback' => 'node_add',
+  'page_arguments' => 'a:1:{i:0;s:17:"test_content_type";}',
+  'delivery_callback' => '',
+  'fit' => '7',
+  'number_parts' => '3',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'node/add/test-content-type',
+  'title' => 'test_content_type',
+  'title_callback' => 'check_plain',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '6',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'modules/node/node.pages.inc',
+))
 ->values(array(
   'path' => 'rss.xml',
   'load_functions' => '',
@@ -19343,7 +20910,7 @@
   'to_arg_functions' => '',
   'access_callback' => 'user_access',
   'access_arguments' => 'a:1:{i:0;s:14:"access content";}',
-  'page_callback' => 'taxonomy_autocomplete',
+  'page_callback' => 'i18n_taxonomy_autocomplete_field',
   'page_arguments' => 'a:0:{}',
   'delivery_callback' => '',
   'fit' => '3',
@@ -19360,7 +20927,7 @@
   'description' => '',
   'position' => '',
   'weight' => '0',
-  'include_file' => 'modules/taxonomy/taxonomy.pages.inc',
+  'include_file' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc',
 ))
 ->values(array(
   'path' => 'taxonomy/term/%',
@@ -19368,7 +20935,7 @@
   'to_arg_functions' => '',
   'access_callback' => 'user_access',
   'access_arguments' => 'a:1:{i:0;s:14:"access content";}',
-  'page_callback' => 'taxonomy_term_page',
+  'page_callback' => 'i18n_taxonomy_term_page',
   'page_arguments' => 'a:1:{i:0;i:2;}',
   'delivery_callback' => '',
   'fit' => '6',
@@ -19377,7 +20944,7 @@
   'tab_parent' => '',
   'tab_root' => 'taxonomy/term/%',
   'title' => 'Taxonomy term',
-  'title_callback' => 'taxonomy_term_title',
+  'title_callback' => 'i18n_taxonomy_term_name',
   'title_arguments' => 'a:1:{i:0;i:2;}',
   'theme_callback' => '',
   'theme_arguments' => 'a:0:{}',
@@ -19385,7 +20952,7 @@
   'description' => '',
   'position' => '',
   'weight' => '0',
-  'include_file' => 'modules/taxonomy/taxonomy.pages.inc',
+  'include_file' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc',
 ))
 ->values(array(
   'path' => 'taxonomy/term/%/edit',
@@ -19437,13 +21004,63 @@
   'weight' => '0',
   'include_file' => 'modules/taxonomy/taxonomy.pages.inc',
 ))
+->values(array(
+  'path' => 'taxonomy/term/%/translate',
+  'load_functions' => 'a:1:{i:2;s:18:"taxonomy_term_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'i18n_object_translate_access',
+  'access_arguments' => 'a:2:{i:0;s:13:"taxonomy_term";i:1;i:2;}',
+  'page_callback' => 'i18n_page_translate_tab',
+  'page_arguments' => 'a:2:{i:0;s:13:"taxonomy_term";i:1;i:2;}',
+  'delivery_callback' => '',
+  'fit' => '13',
+  'number_parts' => '4',
+  'context' => '1',
+  'tab_parent' => 'taxonomy/term/%',
+  'tab_root' => 'taxonomy/term/%',
+  'title' => 'Translate',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '132',
+  'description' => '',
+  'position' => '',
+  'weight' => '10',
+  'include_file' => 'sites/all/modules/i18n/i18n.pages.inc',
+))
+->values(array(
+  'path' => 'taxonomy/term/%/translate/%',
+  'load_functions' => 'a:2:{i:2;s:18:"taxonomy_term_load";i:4;s:18:"i18n_language_load";}',
+  'to_arg_functions' => '',
+  'access_callback' => 'i18n_object_translate_access',
+  'access_arguments' => 'a:2:{i:0;s:13:"taxonomy_term";i:1;i:2;}',
+  'page_callback' => 'i18n_page_translate_tab',
+  'page_arguments' => 'a:3:{i:0;s:13:"taxonomy_term";i:1;i:2;i:2;i:4;}',
+  'delivery_callback' => '',
+  'fit' => '26',
+  'number_parts' => '5',
+  'context' => '0',
+  'tab_parent' => '',
+  'tab_root' => 'taxonomy/term/%/translate/%',
+  'title' => 'Translate',
+  'title_callback' => 't',
+  'title_arguments' => '',
+  'theme_callback' => '',
+  'theme_arguments' => 'a:0:{}',
+  'type' => '0',
+  'description' => '',
+  'position' => '',
+  'weight' => '0',
+  'include_file' => 'sites/all/modules/i18n/i18n.pages.inc',
+))
 ->values(array(
   'path' => 'taxonomy/term/%/view',
   'load_functions' => 'a:1:{i:2;s:18:"taxonomy_term_load";}',
   'to_arg_functions' => '',
   'access_callback' => 'user_access',
   'access_arguments' => 'a:1:{i:0;s:14:"access content";}',
-  'page_callback' => 'taxonomy_term_page',
+  'page_callback' => 'i18n_taxonomy_term_page',
   'page_arguments' => 'a:1:{i:0;i:2;}',
   'delivery_callback' => '',
   'fit' => '13',
@@ -19460,7 +21077,7 @@
   'description' => '',
   'position' => '',
   'weight' => '0',
-  'include_file' => 'modules/taxonomy/taxonomy.pages.inc',
+  'include_file' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc',
 ))
 ->values(array(
   'path' => 'user',
@@ -20309,6 +21926,21 @@
   'disabled' => '1',
   'orig_type' => 'blog',
 ))
+->values(array(
+  'type' => 'et',
+  'name' => 'et',
+  'base' => 'node_content',
+  'module' => 'node',
+  'description' => '',
+  'help' => '',
+  'has_title' => '1',
+  'title_label' => 'Title',
+  'custom' => '1',
+  'modified' => '1',
+  'locked' => '0',
+  'disabled' => '0',
+  'orig_type' => 'et',
+))
 ->values(array(
   'type' => 'forum',
   'name' => 'Forum topic',
@@ -20339,6 +21971,21 @@
   'disabled' => '0',
   'orig_type' => 'page',
 ))
+->values(array(
+  'type' => 'test_content_type',
+  'name' => 'test_content_type',
+  'base' => 'node_content',
+  'module' => 'node',
+  'description' => '',
+  'help' => '',
+  'has_title' => '1',
+  'title_label' => 'Title',
+  'custom' => '1',
+  'modified' => '1',
+  'locked' => '0',
+  'disabled' => '0',
+  'orig_type' => 'test_content_type',
+))
 ->execute();
 $connection->schema()->createTable('queue', array(
   'fields' => array(
@@ -21083,6 +22730,20 @@
   'module' => '',
   'weight' => '0',
 ))
+->values(array(
+  'name' => 'Drupali18nConfigTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n.test',
+  'module' => 'i18n',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'Drupali18nTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n.test',
+  'module' => 'i18n',
+  'weight' => '0',
+))
 ->values(array(
   'name' => 'DrupalLocalStreamWrapper',
   'type' => 'class',
@@ -21202,6 +22863,167 @@
   'module' => 'field',
   'weight' => '0',
 ))
+->values(array(
+  'name' => 'EntityTranslationCommentHandler',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.comment.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationCommentTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationContentTranslationTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationDefaultHandler',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationHandlerFactory',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler_factory.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationHandlerInterface',
+  'type' => 'interface',
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationHierarchyTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationHookTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationIntegrationTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationNodeHandler',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.node.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationTaxonomyAutocompleteTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationTaxonomyTermHandler',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.taxonomy_term.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationToggleFieldsTranslatabilityTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationTranslationTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'EntityTranslationUserHandler',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.user.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'entity_translation_handler_field_field',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_field_field.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'entity_translation_handler_field_label',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_field_label.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'entity_translation_handler_field_translate_link',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_field_translate_link.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'entity_translation_handler_filter_entity_type',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_filter_entity_type.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'entity_translation_handler_filter_language',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_filter_language.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'entity_translation_handler_filter_translation_exists',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_filter_translation_exists.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'entity_translation_handler_relationship',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_relationship.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
 ->values(array(
   'name' => 'FieldAttachOtherTestCase',
   'type' => 'class',
@@ -21597,8 +23419,78 @@
 ->values(array(
   'name' => 'HtaccessTest',
   'type' => 'class',
-  'filename' => 'modules/system/system.test',
-  'module' => 'system',
+  'filename' => 'modules/system/system.test',
+  'module' => 'system',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18nStringTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_string/i18n_string.test',
+  'module' => 'i18n_string',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18nTaxonomyTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.test',
+  'module' => 'i18n_taxonomy',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18n_object_wrapper',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_object.inc',
+  'module' => 'i18n',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18n_string_object',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_string/i18n_string.inc',
+  'module' => 'i18n_string',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18n_string_object_wrapper',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_string/i18n_string.inc',
+  'module' => 'i18n_string',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18n_string_textgroup_cached',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_string/i18n_string.inc',
+  'module' => 'i18n_string',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18n_string_textgroup_default',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_string/i18n_string.inc',
+  'module' => 'i18n_string',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18n_taxonomy_term',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.inc',
+  'module' => 'i18n_taxonomy',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18n_taxonomy_translation_set',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.inc',
+  'module' => 'i18n_taxonomy',
+  'weight' => '0',
+))
+->values(array(
+  'name' => 'i18n_translation_set',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/i18n/i18n_translation/i18n_translation.inc',
+  'module' => 'i18n_translation',
   'weight' => '0',
 ))
 ->values(array(
@@ -21944,6 +23836,13 @@
   'module' => '',
   'weight' => '0',
 ))
+->values(array(
+  'name' => 'MigrateTranslationEntityHandler',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.migrate.inc',
+  'module' => 'entity_translation',
+  'weight' => '0',
+))
 ->values(array(
   'name' => 'ModuleDependencyTestCase',
   'type' => 'class',
@@ -23134,6 +25033,13 @@
   'module' => 'user',
   'weight' => '0',
 ))
+->values(array(
+  'name' => 'VariableTestCase',
+  'type' => 'class',
+  'filename' => 'sites/all/modules/variable/variable.test',
+  'module' => 'variable',
+  'weight' => '0',
+))
 ->execute();
 $connection->schema()->createTable('registry_file', array(
   'fields' => array(
@@ -23635,6 +25541,142 @@
   'filename' => 'sites/all/modules/date/tests/DateValidationTestCase.test',
   'hash' => '675c3dce13d95e424364de4db2c49d88cba19ce8eb86530341374c82a3b1292f',
 ))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.comment.inc',
+  'hash' => 'c1667be0bdea8805be52b10bad904f60a278213b749d4c9903e3887b6165448c',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.inc',
+  'hash' => 'd470382679e75b63f370e7d476a737eb104e7bae0937668d912873bc2c94ce89',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.node.inc',
+  'hash' => '255963e0d0a38349e1c5cb3ff072819b9c39cb5760a71b312ddaac1dd2edbb82',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.taxonomy_term.inc',
+  'hash' => '587417ed1ba9debd4caef9b32722ca003be28c325d12f3fcb82b70384c095814',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler.user.inc',
+  'hash' => '4574d55fc756f566bc10f574231ee7900a181fbaa68d647ae973d9e06b4167ac',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.handler_factory.inc',
+  'hash' => '80ed7658c3d685bc18e7d29129893b2fb48dcda029e730241c1d0f53db7ad367',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/includes/translation.migrate.inc',
+  'hash' => 'e5c56971c44ad5352089481e48e01fb83bfefbb5359c8033949ce5227a917d42',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/tests/entity_translation.test',
+  'hash' => 'b3aa79b5d0805999336c000aafa63c9384303bd5018ca57d78cb94fa380eb854',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_field_field.inc',
+  'hash' => '4681a5b5e8963b562e6fbb39c3385024034f392162eb4ff7f52a1684db628ea8',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_field_label.inc',
+  'hash' => 'd31ea1af45150832df052fad29af729c6c028a54fa44d62a023a861bbeba744d',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_field_translate_link.inc',
+  'hash' => '78cfa9c4e9b074e5d45d887fe8f69a8966fc0112d95a6abc74b9b13421b58047',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_filter_entity_type.inc',
+  'hash' => 'd86bb73731c60f8ebaa3d8c29f837403935e91fe062c5ab6293901aa253da3e7',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_filter_language.inc',
+  'hash' => '44c3a6928b8f7cde5fea6ae7cff973982d6c7781a3c8be663838ff770435c939',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_filter_translation_exists.inc',
+  'hash' => '25d8ee7f5d45a5e3984572d00816368902121574f04277a67bbabd004b67f39a',
+))
+->values(array(
+  'filename' => 'sites/all/modules/entity_translation/views/entity_translation_handler_relationship.inc',
+  'hash' => 'aac136a9eab8c4b832edd73b942e124c3fdf1f48b6c8a8d0bf5383ba1d32259c',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n.test',
+  'hash' => 'c52b5076bfd40ec8820a1f58dd9ea6f8d0098c771f65d740298143137d52866e',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_object.inc',
+  'hash' => '13118a2525f7ef27040f1c4824fcd05258154fcba128bdb5802c0aac471293c8',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_string/i18n_string.admin.inc',
+  'hash' => 'ace6c13b12cbb5379c803def1f4c4ba073457aa7fe84d1f87a4a4693e28b216c',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_string/i18n_string.inc',
+  'hash' => '35569a693d0bb7df4035acefddc026087676f5671f0957ec56bbdd63eadd1fbc',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_string/i18n_string.test',
+  'hash' => '5127520c1b08f78d3314d6df948b6a59eeb5a889c74687a196bfa3244c33f6d8',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.admin.inc',
+  'hash' => '5b609efc7ae96ddf4129dc9ee59a4a17fd522c8f9c6e148ffb1ac6eb24700246',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.inc',
+  'hash' => '7bc643c59e25c8a00491bc70258821793bbcb232a70510eff071a02bb1e4343e',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.pages.inc',
+  'hash' => 'cd0cb343cbfe113a13583450618163b90e6bee8778f79b6dc76ce12dfd13a2f4',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.test',
+  'hash' => '29fc8c69345e4d2b8eda25ae5d7775772ff1432452f07d51d66b727857ba3690',
+))
+->values(array(
+  'filename' => 'sites/all/modules/i18n/i18n_translation/i18n_translation.inc',
+  'hash' => 'e7e38105a080efcb1d947e7e6d2f1487127b166905b2722c516582827ddaf143',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/includes/forum.variable.inc',
+  'hash' => '84ab5992d648c704b2ae6d680cf8e02d3150cccd8939170f7e8fd82ac054b516',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/includes/locale.variable.inc',
+  'hash' => '27173d9c9e526a8ba88c5f48bf516aaac59ad932b64ef654621bc11f1ccd9f7a',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/includes/menu.variable.inc',
+  'hash' => 'bc776840ee32060a9fda616ca154d3fd315461fbe07ce822d7969b79fccd8160',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/includes/node.variable.inc',
+  'hash' => '596064101f8fbd3affdb61ca1240354ce0b51778601a8b02c021a1150bbf4e06',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/includes/system.variable.inc',
+  'hash' => '909bae0f1e3a4d85c32c385a92a58c559576fb60fd13a0e4f71127eee27afd3e',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/includes/taxonomy.variable.inc',
+  'hash' => '7792f07f8ea088cd8c3350e16f4cacef262c319c2e605dd911f17999a872f09e',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/includes/translation.variable.inc',
+  'hash' => '3e4e82f779986bfb32987d6b27bdab9f907ba5e18841847f138a20c42cf725d4',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/includes/user.variable.inc',
+  'hash' => 'b80094c1db0037f396f197bdd70c19e87afe76f4378c5c6089c4199af3bcb03a',
+))
+->values(array(
+  'filename' => 'sites/all/modules/variable/variable.test',
+  'hash' => 'a6614814c24aee5ae1d2f2f8c23c08138466c41a82e57ee670e070d7cdd6e4b2',
+))
 ->execute();
 $connection->schema()->createTable('role', array(
   'fields' => array(
@@ -23829,6 +25871,11 @@
   'permission' => 'administer content types',
   'module' => 'node',
 ))
+->values(array(
+  'rid' => '3',
+  'permission' => 'administer entity translation',
+  'module' => 'entity_translation',
+))
 ->values(array(
   'rid' => '3',
   'permission' => 'administer fields',
@@ -24034,6 +26081,21 @@
   'permission' => 'skip comment approval',
   'module' => 'comment',
 ))
+->values(array(
+  'rid' => '3',
+  'permission' => 'toggle field translatability',
+  'module' => 'entity_translation',
+))
+->values(array(
+  'rid' => '3',
+  'permission' => 'translate admin strings',
+  'module' => 'i18n_string',
+))
+->values(array(
+  'rid' => '3',
+  'permission' => 'translate any entity',
+  'module' => 'entity_translation',
+))
 ->values(array(
   'rid' => '3',
   'permission' => 'translate content',
@@ -24044,6 +26106,16 @@
   'permission' => 'translate interface',
   'module' => 'locale',
 ))
+->values(array(
+  'rid' => '3',
+  'permission' => 'translate node entities',
+  'module' => 'entity_translation',
+))
+->values(array(
+  'rid' => '3',
+  'permission' => 'translate user-defined strings',
+  'module' => 'i18n_string',
+))
 ->values(array(
   'rid' => '3',
   'permission' => 'use advanced search',
@@ -26326,10 +28398,10 @@
   'name' => 'entity_translation',
   'type' => 'module',
   'owner' => '',
-  'status' => '0',
+  'status' => '1',
   'bootstrap' => '0',
-  'schema_version' => '-1',
-  'weight' => '0',
+  'schema_version' => '7009',
+  'weight' => '11',
   'info' => 'a:12:{s:4:"name";s:18:"Entity Translation";s:11:"description";s:58:"Allows entities to be translated into different languages.";s:7:"package";s:33:"Multilingual - Entity Translation";s:4:"core";s:3:"7.x";s:9:"configure";s:40:"admin/config/regional/entity_translation";s:12:"dependencies";a:1:{i:0;s:14:"locale (>7.14)";}s:17:"test_dependencies";a:2:{i:0;s:17:"pathauto:pathauto";i:1;s:11:"title:title";}s:5:"files";a:15:{i:0;s:40:"includes/translation.handler_factory.inc";i:1;s:32:"includes/translation.handler.inc";i:2;s:40:"includes/translation.handler.comment.inc";i:3;s:37:"includes/translation.handler.node.inc";i:4;s:46:"includes/translation.handler.taxonomy_term.inc";i:5;s:37:"includes/translation.handler.user.inc";i:6;s:32:"includes/translation.migrate.inc";i:7;s:29:"tests/entity_translation.test";i:8;s:49:"views/entity_translation_handler_relationship.inc";i:9;s:57:"views/entity_translation_handler_field_translate_link.inc";i:10;s:48:"views/entity_translation_handler_field_label.inc";i:11;s:55:"views/entity_translation_handler_filter_entity_type.inc";i:12;s:52:"views/entity_translation_handler_filter_language.inc";i:13;s:62:"views/entity_translation_handler_filter_translation_exists.inc";i:14;s:48:"views/entity_translation_handler_field_field.inc";}s:5:"mtime";i:1664867622;s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
 ))
 ->values(array(
@@ -26370,10 +28442,10 @@
   'name' => 'i18n',
   'type' => 'module',
   'owner' => '',
-  'status' => '0',
-  'bootstrap' => '0',
-  'schema_version' => '-1',
-  'weight' => '0',
+  'status' => '1',
+  'bootstrap' => '1',
+  'schema_version' => '7001',
+  'weight' => '10',
   'info' => 'a:11:{s:4:"name";s:20:"Internationalization";s:11:"description";s:49:"Extends Drupal support for multilingual features.";s:12:"dependencies";a:2:{i:0;s:6:"locale";i:1;s:8:"variable";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:2:{i:0;s:15:"i18n_object.inc";i:1;s:9:"i18n.test";}s:9:"configure";s:26:"admin/config/regional/i18n";s:5:"mtime";i:1664867462;s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
 ))
 ->values(array(
@@ -26480,10 +28552,10 @@
   'name' => 'i18n_string',
   'type' => 'module',
   'owner' => '',
-  'status' => '0',
+  'status' => '1',
   'bootstrap' => '0',
-  'schema_version' => '-1',
-  'weight' => '0',
+  'schema_version' => '7004',
+  'weight' => '10',
   'info' => 'a:11:{s:4:"name";s:18:"String translation";s:11:"description";s:57:"Provides support for translation of user defined strings.";s:12:"dependencies";a:2:{i:0;s:6:"locale";i:1;s:4:"i18n";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:3:{i:0;s:21:"i18n_string.admin.inc";i:1;s:15:"i18n_string.inc";i:2;s:16:"i18n_string.test";}s:9:"configure";s:34:"admin/config/regional/i18n/strings";s:5:"mtime";i:1664867462;s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
 ))
 ->values(array(
@@ -26502,10 +28574,10 @@
   'name' => 'i18n_taxonomy',
   'type' => 'module',
   'owner' => '',
-  'status' => '0',
+  'status' => '1',
   'bootstrap' => '0',
-  'schema_version' => '-1',
-  'weight' => '0',
+  'schema_version' => '7004',
+  'weight' => '5',
   'info' => 'a:10:{s:4:"name";s:20:"Taxonomy translation";s:11:"description";s:30:"Enables multilingual taxonomy.";s:12:"dependencies";a:3:{i:0;s:8:"taxonomy";i:1;s:11:"i18n_string";i:2;s:16:"i18n_translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:4:{i:0;s:17:"i18n_taxonomy.inc";i:1;s:23:"i18n_taxonomy.pages.inc";i:2;s:23:"i18n_taxonomy.admin.inc";i:3;s:18:"i18n_taxonomy.test";}s:5:"mtime";i:1664867462;s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
 ))
 ->values(array(
@@ -26513,9 +28585,9 @@
   'name' => 'i18n_translation',
   'type' => 'module',
   'owner' => '',
-  'status' => '0',
+  'status' => '1',
   'bootstrap' => '0',
-  'schema_version' => '-1',
+  'schema_version' => '0',
   'weight' => '0',
   'info' => 'a:10:{s:4:"name";s:16:"Translation sets";s:11:"description";s:47:"Simple translation sets API for generic objects";s:12:"dependencies";a:1:{i:0;s:4:"i18n";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:1:{i:0;s:20:"i18n_translation.inc";}s:5:"mtime";i:1664867462;s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
 ))
@@ -26700,9 +28772,9 @@
   'name' => 'variable',
   'type' => 'module',
   'owner' => '',
-  'status' => '0',
-  'bootstrap' => '0',
-  'schema_version' => '-1',
+  'status' => '1',
+  'bootstrap' => '1',
+  'schema_version' => '0',
   'weight' => '0',
   'info' => 'a:10:{s:4:"name";s:8:"Variable";s:11:"description";s:43:"Variable Information and basic variable API";s:7:"package";s:8:"Variable";s:4:"core";s:3:"7.x";s:5:"files";a:9:{i:0;s:27:"includes/forum.variable.inc";i:1;s:28:"includes/locale.variable.inc";i:2;s:26:"includes/menu.variable.inc";i:3;s:26:"includes/node.variable.inc";i:4;s:28:"includes/system.variable.inc";i:5;s:30:"includes/taxonomy.variable.inc";i:6;s:33:"includes/translation.variable.inc";i:7;s:26:"includes/user.variable.inc";i:8;s:13:"variable.test";}s:5:"mtime";i:1664867493;s:12:"dependencies";a:0:{}s:7:"version";N;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}',
 ))
@@ -26905,6 +28977,19 @@
       'size' => 'normal',
       'default' => '0',
     ),
+    'language' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '12',
+      'default' => 'und',
+    ),
+    'i18n_tsid' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+      'unsigned' => TRUE,
+    ),
   ),
   'primary key' => array(
     'tid',
@@ -26920,6 +29005,8 @@
   'description',
   'format',
   'weight',
+  'language',
+  'i18n_tsid',
 ))
 ->values(array(
   'tid' => '1',
@@ -26928,6 +29015,8 @@
   'description' => '',
   'format' => NULL,
   'weight' => '2',
+  'language' => 'und',
+  'i18n_tsid' => '0',
 ))
 ->values(array(
   'tid' => '5',
@@ -26936,6 +29025,8 @@
   'description' => 'Where the cool kids are.',
   'format' => NULL,
   'weight' => '3',
+  'language' => 'und',
+  'i18n_tsid' => '0',
 ))
 ->values(array(
   'tid' => '6',
@@ -26944,6 +29035,8 @@
   'description' => '',
   'format' => NULL,
   'weight' => '4',
+  'language' => 'und',
+  'i18n_tsid' => '0',
 ))
 ->values(array(
   'tid' => '7',
@@ -26952,6 +29045,8 @@
   'description' => '',
   'format' => NULL,
   'weight' => '1',
+  'language' => 'und',
+  'i18n_tsid' => '0',
 ))
 ->values(array(
   'tid' => '8',
@@ -26960,6 +29055,8 @@
   'description' => '',
   'format' => NULL,
   'weight' => '0',
+  'language' => 'und',
+  'i18n_tsid' => '0',
 ))
 ->execute();
 $connection->schema()->createTable('taxonomy_term_hierarchy', array(
@@ -27056,6 +29153,19 @@
       'size' => 'normal',
       'default' => '0',
     ),
+    'language' => array(
+      'type' => 'varchar',
+      'not null' => TRUE,
+      'length' => '12',
+      'default' => 'und',
+    ),
+    'i18n_mode' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'default' => '0',
+      'unsigned' => TRUE,
+    ),
   ),
   'primary key' => array(
     'vid',
@@ -27072,6 +29182,8 @@
   'hierarchy',
   'module',
   'weight',
+  'language',
+  'i18n_mode',
 ))
 ->values(array(
   'vid' => '1',
@@ -27081,6 +29193,8 @@
   'hierarchy' => '0',
   'module' => 'taxonomy',
   'weight' => '0',
+  'language' => 'und',
+  'i18n_mode' => '0',
 ))
 ->values(array(
   'vid' => '2',
@@ -27090,6 +29204,8 @@
   'hierarchy' => '1',
   'module' => 'forum',
   'weight' => '-10',
+  'language' => 'und',
+  'i18n_mode' => '0',
 ))
 ->execute();
 $connection->schema()->createTable('url_alias', array(
@@ -27282,8 +29398,8 @@
   'signature' => '',
   'signature_format' => NULL,
   'created' => '0',
-  'access' => '1675415332',
-  'login' => '1675410565',
+  'access' => '1679731173',
+  'login' => '1679731173',
   'status' => '1',
   'timezone' => NULL,
   'language' => '',
@@ -27414,6 +29530,10 @@
   'name' => 'additional_settings__active_tab_book',
   'value' => 's:13:"edit-workflow";',
 ))
+->values(array(
+  'name' => 'additional_settings__active_tab_et',
+  'value' => 's:9:"edit-menu";',
+))
 ->values(array(
   'name' => 'additional_settings__active_tab_forum',
   'value' => 's:15:"edit-submission";',
@@ -27424,7 +29544,7 @@
 ))
 ->values(array(
   'name' => 'additional_settings__active_tab_test_content_type',
-  'value' => 's:13:"edit-workflow";',
+  'value' => 's:9:"edit-menu";',
 ))
 ->values(array(
   'name' => 'admin_compact_mode',
@@ -27460,39 +29580,39 @@
 ))
 ->values(array(
   'name' => 'cache_flush_cache',
-  'value' => 'i:0;',
+  'value' => 'i:1679731162;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_block',
-  'value' => 'i:0;',
+  'value' => 'i:1679731162;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_field',
-  'value' => 'i:0;',
+  'value' => 'i:1679731162;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_filter',
-  'value' => 'i:1675411087;',
+  'value' => 'i:0;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_form',
-  'value' => 'i:0;',
+  'value' => 'i:1679731162;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_image',
-  'value' => 'i:1675411087;',
+  'value' => 'i:0;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_menu',
-  'value' => 'i:0;',
+  'value' => 'i:1679731162;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_page',
-  'value' => 'i:0;',
+  'value' => 'i:1679731162;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_path',
-  'value' => 'i:0;',
+  'value' => 'i:1679731162;',
 ))
 ->values(array(
   'name' => 'cache_flush_cache_variable',
@@ -27554,6 +29674,10 @@
   'name' => 'comment_anonymous_blog',
   'value' => 'i:0;',
 ))
+->values(array(
+  'name' => 'comment_anonymous_et',
+  'value' => 'i:0;',
+))
 ->values(array(
   'name' => 'comment_anonymous_forum',
   'value' => 'i:0;',
@@ -27562,6 +29686,10 @@
   'name' => 'comment_anonymous_page',
   'value' => 'i:0;',
 ))
+->values(array(
+  'name' => 'comment_anonymous_test_content_type',
+  'value' => 'i:0;',
+))
 ->values(array(
   'name' => 'comment_article',
   'value' => 's:1:"2";',
@@ -27578,6 +29706,10 @@
   'name' => 'comment_default_mode_blog',
   'value' => 'i:1;',
 ))
+->values(array(
+  'name' => 'comment_default_mode_et',
+  'value' => 'i:1;',
+))
 ->values(array(
   'name' => 'comment_default_mode_forum',
   'value' => 'i:1;',
@@ -27586,6 +29718,10 @@
   'name' => 'comment_default_mode_page',
   'value' => 'i:1;',
 ))
+->values(array(
+  'name' => 'comment_default_mode_test_content_type',
+  'value' => 'i:1;',
+))
 ->values(array(
   'name' => 'comment_default_per_page_article',
   'value' => 's:2:"50";',
@@ -27594,6 +29730,10 @@
   'name' => 'comment_default_per_page_blog',
   'value' => 's:2:"50";',
 ))
+->values(array(
+  'name' => 'comment_default_per_page_et',
+  'value' => 's:2:"50";',
+))
 ->values(array(
   'name' => 'comment_default_per_page_forum',
   'value' => 's:2:"50";',
@@ -27602,6 +29742,14 @@
   'name' => 'comment_default_per_page_page',
   'value' => 's:2:"50";',
 ))
+->values(array(
+  'name' => 'comment_default_per_page_test_content_type',
+  'value' => 's:2:"50";',
+))
+->values(array(
+  'name' => 'comment_et',
+  'value' => 's:1:"2";',
+))
 ->values(array(
   'name' => 'comment_form_location_article',
   'value' => 'i:1;',
@@ -27610,6 +29758,10 @@
   'name' => 'comment_form_location_blog',
   'value' => 'i:1;',
 ))
+->values(array(
+  'name' => 'comment_form_location_et',
+  'value' => 'i:1;',
+))
 ->values(array(
   'name' => 'comment_form_location_forum',
   'value' => 'i:1;',
@@ -27618,6 +29770,10 @@
   'name' => 'comment_form_location_page',
   'value' => 'i:1;',
 ))
+->values(array(
+  'name' => 'comment_form_location_test_content_type',
+  'value' => 'i:1;',
+))
 ->values(array(
   'name' => 'comment_forum',
   'value' => 's:1:"2";',
@@ -27634,6 +29790,10 @@
   'name' => 'comment_preview_blog',
   'value' => 's:1:"1";',
 ))
+->values(array(
+  'name' => 'comment_preview_et',
+  'value' => 's:1:"1";',
+))
 ->values(array(
   'name' => 'comment_preview_forum',
   'value' => 's:1:"1";',
@@ -27642,6 +29802,10 @@
   'name' => 'comment_preview_page',
   'value' => 's:1:"1";',
 ))
+->values(array(
+  'name' => 'comment_preview_test_content_type',
+  'value' => 's:1:"1";',
+))
 ->values(array(
   'name' => 'comment_subject_field_article',
   'value' => 'i:1;',
@@ -27650,6 +29814,10 @@
   'name' => 'comment_subject_field_blog',
   'value' => 'i:1;',
 ))
+->values(array(
+  'name' => 'comment_subject_field_et',
+  'value' => 'i:1;',
+))
 ->values(array(
   'name' => 'comment_subject_field_forum',
   'value' => 'i:1;',
@@ -27658,6 +29826,14 @@
   'name' => 'comment_subject_field_page',
   'value' => 'i:1;',
 ))
+->values(array(
+  'name' => 'comment_subject_field_test_content_type',
+  'value' => 'i:1;',
+))
+->values(array(
+  'name' => 'comment_test_content_type',
+  'value' => 's:1:"2";',
+))
 ->values(array(
   'name' => 'configurable_timezones',
   'value' => 'b:1;',
@@ -27668,7 +29844,7 @@
 ))
 ->values(array(
   'name' => 'cron_last',
-  'value' => 'i:1675411087;',
+  'value' => 'i:1679731162;',
 ))
 ->values(array(
   'name' => 'cron_threshold_error',
@@ -27680,7 +29856,7 @@
 ))
 ->values(array(
   'name' => 'css_js_query_string',
-  'value' => 's:6:"rphyrh";',
+  'value' => 's:6:"rs2gzt";',
 ))
 ->values(array(
   'name' => 'ctools_last_cron',
@@ -27730,6 +29906,18 @@
   'name' => 'entity_cache_tables_created',
   'value' => 'N;',
 ))
+->values(array(
+  'name' => 'entity_translation_entity_types',
+  'value' => 'a:1:{s:4:"node";s:4:"node";}',
+))
+->values(array(
+  'name' => 'entity_translation_revision_enabled',
+  'value' => 'b:1;',
+))
+->values(array(
+  'name' => 'entity_translation_taxonomy_autocomplete',
+  'value' => 'b:1;',
+))
 ->values(array(
   'name' => 'error_level',
   'value' => 'i:1;',
@@ -27872,7 +30060,7 @@
 ))
 ->values(array(
   'name' => 'javascript_parsed',
-  'value' => 'a:9:{i:0;s:14:"misc/drupal.js";i:1;s:14:"misc/jquery.js";i:2;s:27:"misc/jquery-extend-3.4.0.js";i:3;s:44:"misc/jquery-html-prefilter-3.5.0-backport.js";i:4;s:19:"misc/jquery.once.js";i:5;s:24:"modules/system/system.js";i:6;s:12:"misc/form.js";s:10:"refresh:fr";s:7:"waiting";s:10:"refresh:is";s:7:"waiting";}',
+  'value' => 'a:10:{i:0;s:14:"misc/drupal.js";i:1;s:14:"misc/jquery.js";i:2;s:27:"misc/jquery-extend-3.4.0.js";i:3;s:44:"misc/jquery-html-prefilter-3.5.0-backport.js";i:4;s:19:"misc/jquery.once.js";i:5;s:19:"misc/tableheader.js";i:6;s:12:"misc/form.js";i:7;s:16:"misc/collapse.js";s:10:"refresh:fr";s:7:"waiting";s:10:"refresh:is";s:7:"waiting";}',
 ))
 ->values(array(
   'name' => 'language_content_type_article',
@@ -27888,7 +30076,7 @@
 ))
 ->values(array(
   'name' => 'language_content_type_et',
-  'value' => 's:1:"4";',
+  'value' => 's:1:"0";',
 ))
 ->values(array(
   'name' => 'language_content_type_forum',
@@ -27900,7 +30088,7 @@
 ))
 ->values(array(
   'name' => 'language_content_type_test_content_type',
-  'value' => 's:1:"4";',
+  'value' => 's:1:"0";',
 ))
 ->values(array(
   'name' => 'language_count',
@@ -27924,7 +30112,7 @@
 ))
 ->values(array(
   'name' => 'language_types',
-  'value' => 'a:3:{s:8:"language";b:1;s:16:"language_content";b:0;s:12:"language_url";b:0;}',
+  'value' => 'a:3:{s:8:"language";b:1;s:16:"language_content";b:1;s:12:"language_url";b:0;}',
 ))
 ->values(array(
   'name' => 'locale_language_negotiation_session_param',
@@ -27960,7 +30148,7 @@
 ))
 ->values(array(
   'name' => 'menu_masks',
-  'value' => 'a:33:{i:0;i:501;i:1;i:493;i:2;i:250;i:3;i:247;i:4;i:246;i:5;i:245;i:6;i:125;i:7;i:123;i:8;i:122;i:9;i:121;i:10;i:117;i:11;i:63;i:12;i:62;i:13;i:61;i:14;i:60;i:15;i:59;i:16;i:58;i:17;i:44;i:18;i:31;i:19;i:30;i:20;i:29;i:21;i:24;i:22;i:21;i:23;i:15;i:24;i:14;i:25;i:13;i:26;i:11;i:27;i:7;i:28;i:6;i:29;i:5;i:30;i:3;i:31;i:2;i:32;i:1;}',
+  'value' => 'a:38:{i:0;i:501;i:1;i:493;i:2;i:250;i:3;i:247;i:4;i:246;i:5;i:245;i:6;i:238;i:7;i:125;i:8;i:123;i:9;i:122;i:10;i:121;i:11;i:119;i:12;i:117;i:13;i:63;i:14;i:62;i:15;i:61;i:16;i:60;i:17;i:59;i:18;i:58;i:19;i:44;i:20;i:31;i:21;i:30;i:22;i:29;i:23;i:26;i:24;i:24;i:25;i:22;i:26;i:21;i:27;i:15;i:28;i:14;i:29;i:13;i:30;i:11;i:31;i:10;i:32;i:7;i:33;i:6;i:34;i:5;i:35;i:3;i:36;i:2;i:37;i:1;}',
 ))
 ->values(array(
   'name' => 'menu_options_article',
@@ -27974,6 +30162,10 @@
   'name' => 'menu_options_book',
   'value' => 'a:1:{i:0;s:9:"main-menu";}',
 ))
+->values(array(
+  'name' => 'menu_options_et',
+  'value' => 'a:0:{}',
+))
 ->values(array(
   'name' => 'menu_options_forum',
   'value' => 'a:1:{i:0;s:9:"main-menu";}',
@@ -27984,7 +30176,7 @@
 ))
 ->values(array(
   'name' => 'menu_options_test_content_type',
-  'value' => 'a:4:{i:0;s:9:"main-menu";i:1;s:10:"management";i:2;s:10:"navigation";i:3;s:9:"user-menu";}',
+  'value' => 'a:0:{}',
 ))
 ->values(array(
   'name' => 'menu_override_parent_selector',
@@ -28002,6 +30194,10 @@
   'name' => 'menu_parent_book',
   'value' => 's:11:"main-menu:0";',
 ))
+->values(array(
+  'name' => 'menu_parent_et',
+  'value' => 's:11:"main-menu:0";',
+))
 ->values(array(
   'name' => 'menu_parent_forum',
   'value' => 's:11:"main-menu:0";',
@@ -28038,6 +30234,10 @@
   'name' => 'node_options_book',
   'value' => 'a:2:{i:0;s:6:"status";i:1;s:8:"revision";}',
 ))
+->values(array(
+  'name' => 'node_options_et',
+  'value' => 'a:2:{i:0;s:6:"status";i:1;s:7:"promote";}',
+))
 ->values(array(
   'name' => 'node_options_forum',
   'value' => 'a:1:{i:0;s:6:"status";}',
@@ -28048,7 +30248,7 @@
 ))
 ->values(array(
   'name' => 'node_options_test_content_type',
-  'value' => 'a:3:{i:0;s:6:"status";i:1;s:7:"promote";i:2;s:8:"revision";}',
+  'value' => 'a:2:{i:0;s:6:"status";i:1;s:7:"promote";}',
 ))
 ->values(array(
   'name' => 'node_preview_article',
@@ -28058,6 +30258,10 @@
   'name' => 'node_preview_blog',
   'value' => 's:1:"1";',
 ))
+->values(array(
+  'name' => 'node_preview_et',
+  'value' => 's:1:"1";',
+))
 ->values(array(
   'name' => 'node_preview_forum',
   'value' => 's:1:"1";',
@@ -28066,6 +30270,10 @@
   'name' => 'node_preview_page',
   'value' => 's:1:"1";',
 ))
+->values(array(
+  'name' => 'node_preview_test_content_type',
+  'value' => 's:1:"1";',
+))
 ->values(array(
   'name' => 'node_rank_comments',
   'value' => 's:1:"0";',
@@ -28098,6 +30306,10 @@
   'name' => 'node_submitted_book',
   'value' => 'i:1;',
 ))
+->values(array(
+  'name' => 'node_submitted_et',
+  'value' => 'i:1;',
+))
 ->values(array(
   'name' => 'node_submitted_forum',
   'value' => 'i:1;',
@@ -28108,7 +30320,7 @@
 ))
 ->values(array(
   'name' => 'node_submitted_test_content_type',
-  'value' => 'i:0;',
+  'value' => 'i:1;',
 ))
 ->values(array(
   'name' => 'overlap_cjk',
@@ -28134,6 +30346,10 @@
   'name' => 'preprocess_js',
   'value' => 'i:0;',
 ))
+->values(array(
+  'name' => 'save_continue_et',
+  'value' => 's:19:"Save and add fields";',
+))
 ->values(array(
   'name' => 'save_continue_test_content_type',
   'value' => 's:19:"Save and add fields";',
@@ -28218,6 +30434,10 @@
   'name' => 'theme_seven_settings',
   'value' => 'a:15:{s:11:"toggle_logo";i:1;s:11:"toggle_name";i:1;s:13:"toggle_slogan";i:1;s:24:"toggle_node_user_picture";i:1;s:27:"toggle_comment_user_picture";i:0;s:32:"toggle_comment_user_verification";i:1;s:14:"toggle_favicon";i:1;s:16:"toggle_main_menu";i:1;s:21:"toggle_secondary_menu";i:0;s:12:"default_logo";i:1;s:9:"logo_path";s:0:"";s:11:"logo_upload";s:0:"";s:15:"default_favicon";i:1;s:12:"favicon_path";s:0:"";s:14:"favicon_upload";s:0:"";}',
 ))
+->values(array(
+  'name' => 'translation_language_type',
+  'value' => 's:16:"language_content";',
+))
 ->values(array(
   'name' => 'user_admin_role',
   'value' => 's:1:"3";',
@@ -28366,6 +30586,10 @@
   'name' => 'user_signatures',
   'value' => 'i:0;',
 ))
+->values(array(
+  'name' => 'variable_module_list',
+  'value' => 'a:2:{s:4:"i18n";a:1:{i:0;s:18:"i18n_language_list";}s:11:"i18n_string";a:7:{i:0;s:33:"i18n_string_translate_langcode_en";i:1;s:33:"i18n_string_translate_langcode_fr";i:2;s:33:"i18n_string_translate_langcode_is";i:3;s:27:"i18n_string_allowed_formats";i:4;s:27:"i18n_string_source_language";i:5;s:17:"i18n_string_debug";i:6;s:39:"i18n_string_textgroup_class_[textgroup]";}}',
+))
 ->execute();
 
 // Reset the SQL mode.
diff --git a/core/modules/forum/tests/src/Functional/GenericTest.php b/core/modules/forum/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..999f8f35297b39ea95007750afb070bde633f90b
--- /dev/null
+++ b/core/modules/forum/tests/src/Functional/GenericTest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Tests\forum\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for forum.
+ *
+ * @group forum
+ */
+class GenericTest extends GenericModuleTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function preUninstallSteps(): void {
+    $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
+    $terms = $storage->loadMultiple();
+    $storage->delete($terms);
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/Upgrade6Test.php b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/Upgrade6Test.php
index a6993ee6d422269d147a7f58d1e8412e579d22cf..f8ba9e6f9b61bb9b5d5cf74b26a0d93124303048 100644
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/Upgrade6Test.php
+++ b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/Upgrade6Test.php
@@ -48,6 +48,8 @@ protected function getSourceBasePath() {
    */
   protected function getEntityCounts() {
     return [
+      'action' => 27,
+      'base_field_override' => 22,
       'block' => 33,
       'block_content' => 1,
       'block_content_type' => 1,
@@ -55,32 +57,30 @@ protected function getEntityCounts() {
       'comment_type' => 8,
       'contact_form' => 2,
       'contact_message' => 0,
+      'date_format' => 12,
       'editor' => 2,
-      'field_config' => 38,
-      'field_storage_config' => 22,
-      'file' => 2,
+      'entity_form_display' => 18,
+      'entity_form_mode' => 1,
+      'entity_view_display' => 34,
+      'entity_view_mode' => 11,
+      'field_config' => 41,
+      'field_storage_config' => 25,
+      'file' => 1,
       'filter_format' => 7,
       'image_style' => 6,
-      'node' => 2,
+      'menu' => 8,
+      'menu_link_content' => 1,
+      'node' => 3,
       'node_type' => 7,
+      'path_alias' => 4,
       'search_page' => 3,
       'shortcut' => 2,
       'shortcut_set' => 1,
-      'action' => 27,
-      'menu' => 8,
-      'path_alias' => 4,
-      'taxonomy_term' => 3,
-      'taxonomy_vocabulary' => 2,
+      'taxonomy_term' => 7,
+      'taxonomy_vocabulary' => 4,
       'user' => 3,
       'user_role' => 4,
-      'menu_link_content' => 1,
       'view' => 14,
-      'date_format' => 12,
-      'entity_form_display' => 18,
-      'entity_form_mode' => 1,
-      'entity_view_display' => 31,
-      'entity_view_mode' => 11,
-      'base_field_override' => 22,
     ];
   }
 
@@ -88,15 +88,7 @@ protected function getEntityCounts() {
    * {@inheritdoc}
    */
   protected function getEntityCountsIncremental() {
-    $counts = $this->getEntityCounts();
-    $counts['block_content'] = 3;
-    $counts['comment'] = 9;
-    $counts['file'] = 8;
-    $counts['menu_link_content'] = 11;
-    $counts['node'] = 19;
-    $counts['taxonomy_term'] = 16;
-    $counts['user'] = 8;
-    return $counts;
+    return [];
   }
 
   /**
@@ -124,6 +116,7 @@ protected function getAvailablePaths() {
       'Search',
       'System',
       'Taxonomy',
+      'Text',
       'Upload',
       'User',
       'Variable admin',
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/html-1.txt b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/html-1.txt
deleted file mode 100644
index 93e18a7177f4127fb1f230f2083b4c563ecd4cdc..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/html-1.txt
+++ /dev/null
@@ -1 +0,0 @@
-<h1>Test HTML</h1>
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-1.png b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-1.png
deleted file mode 100644
index 09e64d6edbc2440d585bbf463c727f51f9b294b7..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-1.png
+++ /dev/null
@@ -1,179 +0,0 @@
-�PNG
-
-���
IHDR��h��������̡����dIDATx^d���eו&�}k�{߻�Ub%�*���%RE�V��
��q�ذ
�aܰ������f<�i�[j�[�-�E��T�X�Ux��{��{���n�w��w���\�@$ ��HD�$HB"�>dz P�1=b��>�rr���(��*�'!���(> ��m9c�
������B1�a�4�ˁ���%-�)��C��T��'% b��}g�ԥX)p9�p͡��3+��a I7
-����9"�epS3�S$%��h���Qy�R���a�s�$Iyca���U�ah��Zs�*+h���ݥ�j�`�K.I��@�DP �F��H��R�q��v�1��4#P&B�E5�Q��+�̓A
0ALJ�`�g���)\��ǒ�aB��;0M��|+`QS
-��� �QJs9E�P�A�$�"�Y��$TH0%3�SJ�P� ɤ��r��RZ<E�pp&��AZ�C���2c�oq�.�И2���RA��.����R���Uh��	]���)�6H�d ���)"�(9O���5yj��Q�h��J�Q�&���)$�@L*��D��A��I�$�c.3���Z��}�B�b��$L�s*�!0�]$�i�Y���(M)�`���0�	�`��r`FJ�s�	JYE���G.dC))AAZ�jw�Z��̤�J$����A����p�T�`��Rc133�.��t'�0Ҍ�e
-YJ�n�C:�h�jNԔM�B4�D����#�$OZH�DV0J(�B�(1t{|�N�u\
-
��gr�H�y<�%A��A�S�R�� h0ys	��6y#�5%.'_Ӻ+45�&悲
-�	��-A��H#]�\1���yЂbT1��`H�or�!V�>�p�Q�$�4�o<G�͋��8<�n����U-��)���n�A
�d�$��8�\�&�cr�R��/��?F
-����r��,�b$�
- (y�M� �V+僠
-�f+�&9�����)�:.�����;MrH&��$3a�HF�0�AK�C�)�F^��0��ҕR:�Q�aH�� &�@̜S��K�SBN3X�	B��#�6P�%�PE�QL����B�H)M:\4��:UR��9J Ƈ�+�9;y cp�����hhf�4�w�adpR4�;��
-<�؝Ba�U��\��pN"�p�̤��T��dl5�p�=&BHq&43�$8pPT�'1�b��P��juVڡ���f �'��r&�HżI`�E"
2HZ����f�9a�u7��{h6+�U��E�.֙�a�UBCJD�9!
�s:~����9Ho3Ab)�џk��(4,<"��k�m�,h(�F0p?i_Q�k�*ҏ��1�"vcr���#M�	eE��C`�aED���Ib�%�`$G�(���4�
- �*�Z�!��������͈ ��C�(��H��4�af�(���F3Z����)u@�����j���!���n�0�ا�c�I*�It)�D�0��D�{Z���n�U�;P�R��u3cd����j^�c�A��L�(9@X[
:�� �F�H���B3�N'�kH�Q�
-pŦx�8Iq�	I���;�U�D-�B�r��A	�Rk,V
�+��{RA�Ϛ�7��a����4-���dFo�)t�t�l{��[z��6�l�a�DDsb(
"!��=�J�j`�8���}�P������U�HJ3��?y�#w4�38�I��&���8�w/|"R.Bs{�W�,�v�14��W����f ��P�Hc>��~������@)t�d,�bIo(t7�b�>\��j}805r��f(*f2��JP1hL(�/��cDuK������\h %��{\j'�h��U1��M+�����SPB�����=�	����V+�&K��迫�B>��xD�B��x�Yz�p�P8ir4�L^Y1�2s��@߯Uj�EI���a(�۾�4%�#X���2ԭF��cd<L��V�0��VS
Qvwх��߷��V�k+(j.�'c�B��[}��S�f�ߒ����0l�w�����2Y�	Hrf���ܬ�cHט �hL6O9A�x+�����P1�`E�4"���@2�_���̝P0��yNi+���L3�*���z�7x�� ���|��?ԅ� F�bC�W���5	�����l�(B�;�c�=]��A�qd�H����頥Ϡ�L
d�tR^�\��B<��陕�7��\�h���j�8�re~u���)�ԥ
-Tr?
s�z���`8�`�b�el��?~�W�����\&�����
-W���ڍ%g!WB��l����k����)R%Z$�}Hu���@��!�*p�)
��rr8��(&��4�4"�re�C0�lHTC�F��܎M��F儤	�X��#�8 ������w�^0���x��l�yҟa	:dXL�d$)?����l�����US��԰x��߼vK�K���u@�KT�=C.��Y�(�L��`�$т��!5i�����OΓId�,8����ْ2�'M��e��e��G�c�|WZt����N!v�vv�ژc����o�Z?(��M�MNK	&X���G�Puo����^���R`�ز<��ߊ�Y��-ขe��S�M��FtP�ܘqG�b�!�� ���=�PJ&��
-�¬��h�����J���Y�F�Ӛ���&�s�����r�����g^�������D�h.=ro��A-Gt�5���[��.\��RO����Z��A_G@����=)����)��OFۯ� KC4���A���$F@��\J�=�E�z�m0�)�����j0A�K��A�%Ñ<i��#�d���<���-~s#4��vp��_�2`)�;i�����s�������=�/W��휸�悗���kE���*���{�-I��� �� ��lwR=z�N�F�&u&B�(�oHr+@+P�_�W�0�U�+y�xr�b�a}c�u	��1#'T�~�W�zP�Z�<}�b�ˆb�@B[
-�X�4�����~��#��(�R���m��,�Ъ"])��IS FHAJ�碥���aa�������p�@���A�2:�)���t����]{�� �k_�؁y4�W6����bm$��O}����6l�9x�Q}���(�ck/Ul�Ǟ������gY�{w�`O]"C�w�[_z���ݛ ��9*$��dt2��bt"�E �/�Hx���f��SQ�����Rc�����|�8������P���}��%���'�%��ͪ7�b���ű[t��a>�BS��_^��L�����bx��
�N5h�Ϝ�xU��c���e{�`�����?m�B�"��W~�寞{��Y}D��s"k�!�&�E3��+%^d���@mS���"���ǽ^	Yr��;f�I\n�g<x�|��
�D�h+?���o?��	�	0�L�~���>�ݒ,"\o��i�?�Fa/�������3<�����C��h��+�g��c�\\����n���'f-�mo�K�g�8�dP������OwJp���
-o�
�Û-rN�3S�6�L$��KpDz�=��0��F�ړ%)�p=y�4�bY����}�s��~y5�8��߻��֩k����OZ�֚=����y���n��f^UW��I��\�j��N���v��懞ڕK�\^����?������zv���պn��B��΍#�����9S��Ͽy�Gl��9֤J%�b��^�Fk";H�@A�S�0,�$8��{��b��	B�I�-���??�o~����D�������_?>/t�]�5f�2���G~��\~m�X�cD ��|�7��qg6_�%���K
�����W_�#٢
D����������`��|H�؜y�Z
E�ac8�u���@�[��3�x�X;Ř����`�h��q���*��go�$ko�!|*4al�z �ضW�٫j��������$��s�W���ٳb	�4�C#�8X=�лW�����طn�W�0Ƕa�n�w�����α2XnH����rsбmls���%�G��x�����gG��9��n����=�p���w6��G]Zi��'�G�D����D��s(�:=��Q��]'�*i-��&+�G
-@'\S2M�8ܽx���v���~X*�x�ͻN_x��C/�w��2lק�냛W��M���u�z�O<pͷ8sro���j�:|��`R���84XD7���~���{�*(�?��Ǘ*oBO�Ӽ6�7ugU-�u}�6�u4�p�]x�b2����"'Lj�`�J&�9ha�6'� ��L�i*�%e�I�!�I��&P.�<+����n�4c���pu>���흭O���9���a�B�R�l�?Pl�6���_h�4�cO��}��|��|u={�{���)A���l�:Ah��gO=��/-�7C��7����/O.�rX~�[���۳g�\�:�w���4k<�f�P$b��/���ݓ���W�#`E[TK�g�	�ZRt,�_��z�d��M�%+n����b$�8A	.F��edLs/��p�U��'���7��1l>���Ɖ?ֽc��|��Y�	��lEYi�ͫq����px�6�������}��>>�_�l�v��>��	F��x��(3�4���7��տ�����K���N��n}������o��Kv��|���gu������,�p�:��F9	���G���G?�p5#c��R4и��PEc�¡�d`&�Ƽ�1�Y@�S^@��	��>#�3�Gt�ir�@\F�U'6��n�nlȾ�;�>���b�����@	k��-\=ѩ���n�@
'Oηn5�c߬~�问�����۟�g�"�v�O�6� �yb�~���n��&�f��q�������������٭;2�_���v�-��pV���m��~�;��s���'A�,B�ɻ�#Q,LB0Ir�ba$�o-&ňޘ*Lila&F�@��z3�?p�	�0ۺ6��/��]k;u�}w�G��l�� K���?u�hǿv��#7��ډ�zk�z��O�z�;������������~PV���ֺ.w�](,tm�ssk������U�?�Y?q�l�_zWW����
,�z���X�z�����(�L*I��#�U
-t��#1y!L���9g&U셓Dw�h
Lio�Ag�����
-����"��ث2?�=q�Q.�z�
-ȡ4W��i3]���yg��t�v�M������.~�����݃G�|����9sow��l��ޙ5���\n���.�
-�(;�K��5�XՕN\3�Y6.��678?���L�s[���$�x�����t�"�:�(���	�L�l��2
-� ��8	8b�0Z�`L|�B=�EN�siL�	����Ԋ�>���q����e�Ú���co����y[���ޏW��(�ko?�g{�{7?ۻ�ԹK����7/���r��{�٥���g�Z޺3p��p��s/�7����G���#��:p�G�\������E�Z_;8wim{7���]�*puf{鲍�
-w�����W~��&+��"F�VS�� ��Lo��w����e�L��̥U9&{��Q�e�$z~��"�E5��k���S���F9�����l�o�{쁛��?���Z7*���\��T��X
��}��[��/�7��f��˽/�i�f%��
-o��'���eu��������c�ݨ�k�����&P���.C�[�lp�_��� �pw(&8,g���LU�|+�2� ��/H�;#ܧ�'��ǃ1�w$�_Ż��,�T )hm1¬#F/�\�8u�GR��_�߯��������f��_�ls/O|�?���~�߼iM�@D���IΟ�xp����{s��TY���o��k���64=v�� ����?s�VW�A������_������(@7��d��K���%&*oU�
-���B�H��zFk���0x��`�A���������Unc�D4����Y�;�n~H4,/�\�ֿ
o>�A����O�O$��KWB `�a�;bv�ƹ�$���̱�J���b�׫ ��[{cv�zty{�&��]�]H2S�����I�!F⩾�bO��	�3���u6��� sfZ��L]�S�(/�����~�Vm���`%��F�/^�:ȩm��,>�2	�J.�)Lis����_Z��P�V�lPl(�0~x��(�C��y�v�zDj�s���-;n��"E�����y;��ʔ�"�"�Y�ve阓���N���$�<)
->�e37s��ݍRnsFU7s������D��u٣w�r��T��;?{��o�k����]񶘷��Ч�t�&3b��<�������杧w�!uᑀ�
WH��Y�KPf�FL�B�OH�)5�k�z�_A_H��`Nc�@���g�
`s��zm�.�:[C�S(:���M��淲���_��v�;w?"�*��<�V�6m�������z�0?���/����S��4��?c���z}�h��Ȋ"9����c��n�_�*�� ���h9��]���1X����dN/>"��}#L����@�����Q���p�*{�Q����y�ѳW7;�F�5a��/���=�`}��OZ6B�x�a����ݤ�0=#
� 
���5U��nA�d;�4G�4ϖ��uĤbY]4Nh3��@:O;��b�)����c>��"�TkV6�dE�rK��
-
-d`]�%Z@洦����﯃F1���~��l��$3p^��z���w�;�):��)%S|����1�Wn���Z�?)�>�t��a����}/�,�?SY�{������	�1�di ��I�S�	�%�{L˥!羷������/s�8��O�
�q���w�[;D��{A�)��v�\�(oi���qY���MbM4&�&@��I�Mb|@:{�We���N=U������$E���$d��}�|��O�Կ>%�Q�_�cs��O��Ϛ&�r���dZ�g$j�3бB��ũQ�����
-��+М�	����ޞL��	��5 N��U$v8J�kR�����'�#$e LJ
�:��I;vA����]�S��usұ_��y�:MS��t����,{QJ�Կ�7���'��)g'�� u� :���VI�	�hdC�U�N�tp�R�E�d����QWG^Lj%�0q�]�&u�Ͷ����ŭ�Mh�#�(�8������eU#ڐqgz�<��!�eO��#��y?�f�Nڔ>0
����#	,�FM� I�ΎSh�
^�����sQɬ�C$(΂�&sA���LhPn}d�+�ֱ%�C���k���R�S)c^P����|J�F¿�A�h�ք8��	�TQ�˒\fdǿ�R*aG
-���0�щ��O��Rp�N��2V�$�0�%��VV`\�#<ꊑ���,q���^�VJƈ���;�U*�]="��-R�R�6�_��P�v���
���1�1R���
-r��3Hp�j`��B���jR]�
-6�4M���Y���N�&6f��X�ﲮH�yC��KiG׻��dTc'KOkD��)��'hI��hN�O=�	)� Y������H�ͦm�43���w��V�HWջ�M{��_/t%d3����)������	ba�Y��`=8��<z�ۡ���/MY�`�3�Kv�`��)�DI����nH�&ѭ?9*l�����Ё9$�q&�\�r䚤��YM
�)4�B�(M
-��q�$#��)1�i�TR1&	����8��Q�b�'H������'N�����<~����h��)?E�7�*��D���WQYf�QB�t����].�
4Z:Ғeb�ik���V�X˼��~�ՠc��EE��u`��p��3��|���Q䉖t1mM�]�E�6����7טzR������v*��|)�ua�)�;�Ӛɫ�.��ю9�Y�I�M&Z��d9I2D�h�IJIp���k�*N�#A1�0)��@����s�2<����'���(ɖ�S����Z�j��F�y�Q2��/�������0H�	rw�֪���i��'��4Ll�G��)��
-o�DQ�\{�sp����x��t�8��V)$guƇ��ᯔ�A���Tn�
����7_���K���p��#w/�:`cm���>楄�0�����*���/�<~Ղh������h(L��{�I��n(%4lS�Dq��k�T��0Fz�(�Oa�L���$�=�mI�Q$�ۄIr��Ifx����g6�af���Iý�[-[���2��p��]T��?B��T���(��,v���aI�9wTo��`�z=,ٳ�4�i
-�2��px�����oLm�H�Č�G��q�H�(`1�Z����K�A݃��H�%;v�#�9�'ŵ��� �A]Z��¹{ۛ̚A���?/U(Z��]�������3�t��q���<#T�/�ik�`>k�Dd0��&�NΝ4�h%��C���dMc�O�ʪy�^�5���3n�5ʃ�2p���̽�N|����&�c�.��~d
��-9�Ԧ8�Vv~={��,V�8��齝��뱧��L�n���L��ѓ���5�O��S]4�O�|�K����&D��#靌�Q��aƩ ��v4I1W������K�ӕ�B��Z�� �<'�@�;�'��ؐs��d�|��:�94I�Ҋ��n�����_\�Q���ç�]=h��߽���[_���@�����n^_�1�x�E8�x��PL����Vnˏ���Ʌ�re� zV�e
t�f�M�6�K����1���a��C��Q�H�Ṛ��9h�^��Ɣw)���\({�%ļ�BF#aݍ��.@	�$.��6�i��G�>��wh��{��O}��^Y?��e{��?�J�7/J\���.J��03*� ]<|��V6���w��(<�\�V��
`,Dw6���j��l�3Bi_�|�P�F��ќ.�
-.��Ӑ�K�f4Hn�X�b�!4����C Ax���
F��,����JG��ԛ=���o���������aM��7�9���Gn��W?;��[����~�A
-�;W>^��v�^��]�}��(n6G�yf�#�yTlʌk�m���f[��cX9,pY��
\/�5S?2	�
-*M]�-Ġ�sp�9HK�1\��8�y5fhFI�)ܡ��fu,��]##��p�5�)��:2``e��3���lw^�C�h7�Ћ��k/|�­���Ƣy6@�ܖV�V�r� ���Ig��y��>��U��+�Vbی�)����[���6A	:�����!{��[�4��)�a��Yѐ��U�„Z±��Ie��G�nf�b�nє�
dP��]�a���v�a_�kY}���#m�imfP1]��������߸�2涜�cx���PH3���@��}*�s�p���ΎbX����V��m\��l(�|����;�ΐy-u��-�z��8�A��4zS�{F0F����ގ��VI
-\a�w$�"��D��Y 1Wd��kVb	��<Ȕg�@LKo8�ݨ7��9�栅����w�.w��õ���4��+�'f��s;��'����~���у]eE�ɋ����ë������C����3�lqdQ��V������
-	.���^T��F�F"	
�����7��lY��MKM$��`��bq�8I�&��S�Q�O�9�e1m��/��V3Q��Ϟ}��c]�k�n|B�����:`A�{h.�77n����O=���Y�+����|��"�ʈ�g�����zk�x��#3�����|�>\�P"+�D�!ҤeV���`B.B,�V�.���J0Ab�"v��4332`�!��)b��М�^A=�ux��i�aA#tg�=���vk�觬M���|dx��EC�6�v~�&�(��&2X���It׽ó�T������a���ƃ��E�9�"6�}����R�6�����<�Ý��[�`��?��f��#F�QZI;�TUa��ɗ����
-�i��}@�D*���\ K1��)l���E�E�*�i,0EZ���Q�q��P�h���
g�~�6�q��Y��ҡյ��%�Cp%fË���͓S	��ݽ���+�⅝��º����wZY_>���z������{�P��W��s�˭E�u/�����4���s?��&��l�����f��J��i�J�A��f��cI���sȉF����� ��h��#��c
-@��l�V����@�Ѓ?�[]76XDȚ���A���BIjP7g{��Z�J�:������u�����y贽����63��ƙ����M�"�
����X��"R��*�p�a��>���b(p
-�K��t� $[�Y!L.��d�U�E\�����{j�a=�K��h��*�
�(3�*E�0d��MN�i�1�(/�Ol�έWM���5i����	�v?y��3�������[�{�3_4�$ǽ�7�S:6�
Κ�_΀�)�4UFkJ�2�F)F9�1�����A��4��
-)��J%4��O�15`��KPa�_"�Aۙ0�
-��sUl�Rem�g�fTj%�B
%���f���.wo�=�9u��p��ۑ��ԕ�gm9\��6l,�Z���}�k_ܙ�7��5���h�@B�#IS���U��_>:]�c~m�>}-��]d��HX\4d�9]P�₊<f3��`}DZ��b�QK{�~D7i�irR05���=\���/��e�B���3��xH�����m�5V85��ްٱ�/,����~y�?��w��/>��_��}iq��]�|��Ͼr��E)ýw@�_���v�����ZJ5u���UH4��%8G����4��Q~��6��@�Q��S:
�@�҈@,#�+0͡s�_!�9��O���fr	�D1���t���jhq^
->�3AV��V�zcfe�>�rh��_���_9���K��<���w����m�v����lܼ53����ae[�T�sf!M��-|�+�����N������78�@8,+T(�E]8$�
-�e[��u5�xt�H1��4�d���^T'�f{p��^c�l��C�ǿ�Zk�H�Ɉ���B�˥���F�iM֜Fc3�����?(��o?z�w��>]k ���Fܳm����wI��� L6Gr}���+��9��r�=����O�䄓V Wb@�>��)�FI�sX���`��|Y	N�b���q�9���U���L��e��˧	D��с~�@B�V�݆jK,,�F��{;�0_���{r��[�Z�V��k��$3E�愥_=�b��.^��
-F�i��8��F��0-f!�:�v
-��w��k[�U�[���$4E99�rҔ8
���Fr�0��$���}hB�W�z,N�����YA	 ,T�����F[����.6TWu9��g���25�����IG�J�n����T�F@i��v�(�S��r,D��k۴{���k+���R����em���[�b��71vt���X
-�hX���<ҁT%�HP�N�%0:�\7„`a�/+f�h3���J
(���=$���D��{��.Xkb��@���s;!kbfC��L�����\�=9����z�l���wgۃԆ��[�������z4���UǼg�Bg��tk�B��rtg0w9�ӊ���ށg�*���r���Ģ�c+E��B��	�;���c�!x!���Ͳ���d�����hkT�]�GAFo7������rZK�LcqM���`pl՛w�b�`E����7���{��#�"�@�iGF�b�3i&Q!�LMuip�܅�����@��%�<l�4cPR���{ˌ�bh �n�a�Z�#�Cj��%�$���#$|	fT�1(�Q���p뀯����*���N��b�:h��h.�UfՌ��ް(���Νzduw��<J��]Q�aeX<^Y�v-"�WzLy�ܰV��!�6bo���l���Clt��|Jb��1[Ի�Fǟ\��#�����2־1���HٓG�6�oӵ�jc��hh>�4N����
-�D�E��;L� ���"z� >� �rۻ]7���Mh�6��q���3Ӻ�O/�����evO�1�0���<���,j��O���ǯ{��� ���a�c�^
-�@��U�/����?�R�<����-���ff3�C�O�X�)��F�s9T�3��b����Z�z���:�p˃�n����z��b���X5T�m����lyrn]��a� �C���RhdȃB�p����� �o|��%dS�D�'$�&vhts����_�j�.<���o���&!�/Ĕ���iI��Sխ���ye<�Odj�j,�����N�SW�����|u�������Ʀ�7��V����}�������c�KB�:	W��K��˗0L%w�b	d�h�,Y��W?�!@�A�4�F���d�T �4�C�����W���cϿx�]�����A	/dգA��y�;��iM�����Ʒ&�����f(��Ԯ���f2	Z�t�ɛ+8#H�����z�,;��Ϭ��>��4�Y���U a
	l�bH���&b�)�E蟘�B/zn=̈́�������A�DeM��y�=n���>=�eNC'�2�=y�ܓk�������Zjd�f{��`g�B��,B���j�/BȆ>dzo1�Yp�!r������*��U��NP�]���6�1�I�T#'�!aash0�C���|�������;�z���j�.�o}�ٟ����S����rL�)N��~�*U5�$�o5�T:��r$Î2����N���
-�.��[���Ν�E�^4����Ѥm�k����=�fE5�m�7ͦ2��Y9M��LcV��z�15X	jK�)UO!�ڰ��Z茋��4���{���ǫg����w�̭�N>��w������A)��)#uk�U⧉�!�p ze?�M%-�L�sNX�P�Q��·D`�׫�Q-t�A
-�Q;�
�j�@|/]��W��1,J��kgT!T"D!0c@d;�;R���l�i%ui�0�Yע��6�Yk�%gTB�S�a֢�g���������������郇��}]��U���.���옽�\�+oE�dh��[#��!��B�*�s�X�&ۀQ��+hq��9�\��v��CU�L7���vw����rĕ'	<ٿ\ ��;�/IȀ2�� ��04�D��ڍ��$_M�yVϤ���#S�ۗ������?y���H�||��k�r8[��?��bH�c�j���PH(B(JTS
-�(���q���'u�r���)\�z1zJ4���3H>�����8dT�t��[��T�N�U�ћώ_y���_�ữ�#B��Z���W��Z��Cɂ�1u��^U�l�����@��y$�� 
-R��b��E�zh�һ�������xi��'�۟L��|�����ZG1IH��	��/��2�?5SI%QXc+q\GɎ�&)9gʶʶ�0̂
-�J�:�#�����v�����ڵ�.��=ޜ^���}xe$����??|�{��	S����G�tJ������������\�q&�W�`'�����d�$8���Zw�	����T�
-�����|����
-�����k�9TQx�߼���&oN���0���[[������tuVb
-l�
-��/~鯢O
-d���D�MCZ�:
-�h���M�¼�e7G�$I��V�����\�^/.���at�wnV��Ģ쭖{�g����7��՟�9_����������|�zz~��1XFE��N엿�_���3ը�Z���Ow�֍�$tv|}ճ{���
����{��o�'@��W�����p6�t�������ݺ�n�.>�����W���o��
-��������$e�����H)	H�a�j0�<#�>,�Z�.�#㸳.HYh̢mD�bE�F�~�v�����Xt�O�˽��>ܱdPj�,p���֛�W�sG�K'?���*���=���6��e[c�	�sj�_�v^^��S��&�	X������?}��	�w��o��?�g DEl���Z���T�!��{_�1��4��;7�rV[�����޿?��}���h�?]���#�E
-4}����dO���'>�ԉ�������*�LA}j�`�:�i�M����}R|�� �������2�WdgSt2��hƳ��hǰ�M�\8��_ɫ����������k�t��tmu:�ks�V���!ceO>�|���`�Y���Q}��(�G����ŗ����x ����{���G/|� Q?~�����=���\?���� �h���]\,{o�9m��?;�pL=#��ȱ�������(8�X��%��"U=7)o[`��F3��fX�c�U���b	���95{������|���4��"�r=髗��y�}��ř�:�O�_�:��������cR�x�?>CG���,?ytՂG��5��}��Iu|��'��߿g�f���l��'�������v������x��7��/��s��?x�
-`O595��j���m�K����r��[0Z��]�. ��QV���B���'�_Fi�T>&�Ic�)���5
-B�n�9�8S��`i�-x������Y5�&���+B��FC�T<���/_<z���I�����O~��N!�>��RPB��c�A��q��~z��~��/�?F���Ӫ:��G'��@�^<ۘ[��ֿy�t�����m�dr�Jջ�#T}]�FH�L��
-�����]L܍��^v�k�.;i�WJ�"-�:8�Q�� �S,����h��<BRBH�?�T:�ۗ�2Z����:ծ��z��ΨAv^�	XC@m]m�}/;ݦB�٧�:H�	����?:�(��=��U�B��h�Ni��|~�����G��;O�[h��
-�����~x�8Y}��?|�7.����*0��A�v�
��������1a6��
~����z�_9�)"�h Xn�Qxq��!P·Q�O|q[i�6)b,�E�f���!�,�"�uX0 �ڜ���l�|�֤nȣG���|��ڎ�bF�+%B`��Ҟ���7��|t�3 ��D?v�<��gO��<&� ����̾E���?��~�
����ӳ{�_�����3��@,A��7�����+=���c�ӯ��/���?6�w�{m��g�N�}��C)�(��$�ٷ�}Q�`��,��X��q��TTj��������w�^���C`�i��=����P������S��"%&��#H�_�4�7�R�p��1�A�iXp0����ˣ�M�tr�Ԇ��w�GgJ�{�ڵ�g�g�:szmtm}4����ߞ}��ɉ�v��k|zSq�BP����W~&�T`T���/plU5 >W]!����3D;��CH�}'�_!��G�+���i (����*
�7͛�����$jX��
N��?�
fs,%@���E# -Yvƀa�Y'��k���#g��ٷ�75�
->��n���'O|��z��W���'�����?����{ͬ��G��U�@U�'�lI�x,��0*��;̉,�Ii���m'���i�b(�Gdݜ�5U�@��\A�M3���4ME�f�k��J�/
-�݅fb�@�Prx`���lɌ�#��hF�
-� �z��(���d^��X�X�5X����f��=♊e�]�F{���ß~��j�������6b�����}��9n6��?Uc�KU�kV�9Ұ-�@���H�pM�r�5�#�:p�k$3(�6����D4��͆j�K��胠�����[��SaLpBe5B�c���$4)�7����	�j�-̄��7���w�|�&8����
-���y�~�ãN���]U�!mܽ���k������@"�Z�V�p�
�ɸA�n&.�J*T�$wR��/+ʉ(E!!1X�9r����M�Ԏ�K@-k/��f���O��NO/^��Q�	)�UFe"I��=�Q�{졈
-?m4!� ����r�r��A+�oܾ>��8��j�|�9ׇ�����Y����@�4z�z?>\���,J�z]{p�HJ�`�
-iF�fűb7m���K3�_(�nƢq�����@ͬ�h�,˵�0z���D`B�?����.O����%�P~�TYQ�e�ieQ/�h�|��Eq�!%5=��7�fX��=�<�ק�+uj��"�0�W� �D1�͓��X��{S�1�=�Ϻ�y��T;T�z&�ܽ��Wk!�ݱ�LՓb˙�;�{�d5Q�Re�[��I�^�h�eLc��Y�#k�h������'�����w�������ݯپ��r�u5�A@ġQ�Y@��1q���J#��/��|��(��_yk�VH}�Z7Z�a<�Xh��D��.�U%,�?��	�K��Tae�?f�1��o��"L�u���g5h����Z	���.iaD��&�ȏ2>TH-�`Ӕ9 ����|�R#������������
-��Uxax��ڭ'瓗oN��B�� ��2�D�3�lJ�Ib`b��(�*�>Z����{��ՏF-�`��xKA@;4�zQq�>?��9NH*���B`������V�׏?o�h���������!poia��Fq�%�a���r��~4Z��Ƣ�,�O���ko0w�6��'�G��{;'_�{��].��2KU)��
�M�gB"U`QT$2ӫ��UD�j�����H@�TU0���p�0a��:=�T4�Dl�"���M�(tR5`V?m� �?qH��u�J
-PB_�{
�E��ŅN[�p`Qs9�ڊ��.%g�~,*��dv`�L�o������du0:��&��x8�Θ��e��U&%�4�CD��'8p9� ��	�3.)�����k��x���@� ���n��,�y�~�F
�Q�|���ώ;PPcMe��aKR	��,���z&U1�
-��2�v
-��E��C
c��1��K���(�	1(�[T�UFD�����Z������?=�K�^����w_���k3y�Of���"��¶����V��@O��x*N��N糛7o\<zu磧�Hz�$\�N��Eף�&4�d���`|��������j��pd�VLL�D�'�|�aVxwI���g~��<`�s��"4��t���@q_�R������\"H��xo_�^�����xְ4/�{�׼;�ka�����rBW�7��ɭ346k�EQ:��ׅ"�C������P����������q�3��b�n���o��@*���r�}:��<PS�V�ڣ��>��U+�~�i����
��{�Uʏ�xliЖ�h������P����GS��tX脬-3��R�$=֓��Gw��f�]5~�������d��T��{�-@�����gI-1�P ��<g�A)� �F<�����ƭ�_����^;�t0�|i�Ϳ�Z��99Z�{�b�⠪g�J�{ma=5RO`t��������5�HX�W��9����Ʋ�C��Fy���Q0w J��b)a���t[�(�?�"{@������Oޭa|x0=ϟ-����/�GԻ��b}�2Q_��.��L�D0	�s�
-��?uc�lX=�[�Sq~�w�}X��^���a�	������=k �ű}0j/��ug@$��Ɠ1۪���3{iF���_���߷V�R̆ p��+WITC���3�$y꤆G{�&&�%�2!'2_!�30|�7I�%�l�f�=ݜ�Vl�iW�`CSC�'���ޯ|��%�\�������Q!�?�ِ�j�}������u?}���_���TN]�f��ܑ�ă��G��f�D�WK�f6mV��i�sF
��<گ�ڄ�';���@� �����GS���'�1���z�v��n
g�Oi
-7�h'RD��pFT�b�5((����˳�M�=U��;+�6�����^
��[X7S�}&$\@*r�%5��LN6�4Cd
�o�~�����W'�x+��o'�B���g�^G*�=MvV��_^\,�6�{;c@Ue����yA,�$�p�z�Q�T ��Pm�bq
-�Pv�}����c���)��rՒ�Q9<%�� �imA�i�l����؞Է*�޺va�;g���!ջM���?��8�(P��q���_J��允w� �"�._������P�7�i<�U3�E�|�z!а�����j�n�x]�Ϛ��:��h��i�gqC�Tb���2�J����<t���0J��u�֗~v��E}af �0�=�x0�܍�ym���1��"�;��hw�x���3a��\��6A�b�p����ź��CB�`X{6�BS�p#���2�J�/����f�BTe��ZT�@}��OnK��k��M��_��{Q��ƾy�4PP#�HX�Z|a���AG��#��ǁ��6"�{	J�"<�hJ
-�J�կ��=����E(�@h�ya�Q��\XD6~"���tO�wfW]��X�eq�t�:>�wF��/_�{&A��
-�0�`�?��a����a�����g#��4�GQUP���s�pwozV�mk�/���%ux������Y;KR�q���AAl�Ҥ֠$j@�'�!�r�fS��� �������B���g���6>a��i�$�F�'qy��a3��S�pDJ�3�3����7\m���Uxq���O?17�����W.}��h��@y��9M�w�
�n���W�7hB�{�AD�t�}
��vg#vó9�n\ �xu��h�[�u�,��^���/�1���X1�ӹ\��D��,�PHh7Yqu���+m	!b<=��ck�W���Çg�#V&�1�3���n��E�H��*���Wo��d�r���ݏ�����b����j݅��xˆw�?�}�N@b"*�F
-�4�q~��[�Md��<������>����u�w~\�@<^�DF=��f��ˋ@/��7L���@(p�� "p-��>����~��6k�/��`�j2]EDI�O9��?j���.Mi&�
-`
-�E���#�� &�;ޖ@�\펯�:�1u��yi�&+�۵��r���%�g�n���j@�@���}=~�钀4�	\�	���7�>�q��FH}Ţ(N������AVg�q5�|>�8�=���̪���"���v�7w�l��C!c�i���B��B�nߡ��7�����l}�����&��'�
:y}�b8�kPQ�ɣ��_��@���]5���� e,���E5��
-,�&��Xxr���ڦ�>���iU0�f�<�d��ˇ�`:�>@��@��@������Q'���T�Q(x~�7_�������a��rI���ōڵhd���?��Ճ9 t�$����պX��ls������F	�^xkt��/-��h�m2((����k���7���g?�?|�g���Ɠ�p����O{�{����Mߜ����b=�U�=s�|�Ѳ�))�j0CK􋌦��LAD���y����d^8�t�m�-��i-�<*s��iu����l�Xu�6R�Zq�n�.���ե�R�„,���������Ë�����O�a%�l-^�W�R��>�ī�l�~�?%�ja���C�.y.�iv�����S���?��폡W��LY��Mn_�����'?�����3�����L�޿�ŷ>��_�;^7�
-��������_����?<R4;��m<�PՀ��;��T�Ai��:�@�[ Q7h���p����ɥx�ր��6m�����ryr�LgF����"V�$�u�����@R��!E���|��n�R��+�F�QV���hC_T��prQ
-kX-�ig~~�=��x��&�+����ƻ��'�����&PX,[�V֠BQ I�����<]��}#Ͼ�?}�˟]�Q������������'fs;������0�Z5W"�^�������6kVI�Y�'ΰ�~�"��?�
�#x�VS�mw�`����Bu�߽v���WєD9 
@}wP?��T/��?`��������ލa�u�F����ܥ@�Ai�q�ԫ��'p�{���w���W�&���*�P�����[5�����mv�T��g7y�V7�v�Y��:����13}��?����;�}pϊG4����ۿ����w�q�iU2~�����������G�0��I5��L�w7Р��
-�"R��Ӆ`�2!���J"�j�h��x>����Ŀ��Y��%�+�~�K�6
�Pt1Febog/���KS�@o�g�/Ը	D ��)�VX_>��;8��_����oνh/���d I��JV��LFh�����Q�G��|/����/AV�hCx��N�E=�˯�L�|��'������;�1���}AN�'�j�`�⏙�"�)��1�Kv�X�G1�)�F5mҡ�_Pa"��+:PRe�p΄�էm�p�W�ͷ�Z�\^u�#ŔC"$'�w��#@U�]
��⪻_��m
-��lP��Ћ��8�
-������vz�\���h�a��(��t�:y��t1Yz
-0����#��F�'>�r\U�}�C�ڈ1���u�\T��;{��gߙ�t4�{g��|�G���&��>.t�'�&P�<c�>o'H����@���J��S��FW�����tgZ3=/��VW�[.�+P�w��L�]g�p��ޙ�<ڠ൯�.���e�wقP�ԅ�A=~p�;��|���j���Ճ��{|K]�z�a�Vs{�tL&Y
�W&�s{B$�0KS P��g��;�/?|����B��Lj�C"��̏�£���"�j��'J%/�\�PAcٍQ%;��h��sT�'��� FQ�S�f������hgw6��-޿�ە`��P�t��S����#d����ۣ���ɭo~�1S<�ŋ�j��@��K�Q#UA/�������Q3
�>
-8Y�����3��ȤWF��⠈���?R"�lv��<j�3��G�>4^?���)���w���'ߺX�.7��T�� Efj���
-I� ��?� !o9
-Z�u�m 
�IEq������+u?~EZq�-;�bUJ ��>=�
����U7�&7��uP�D�Ԇw
-�j��k�PD����;7����{�}�ɧW���=��'=&�	xœ�ҠL4$c)��`���H���+�������G�&������Q��׿��LCC&�7$gPL��\�jQA ,P1%�R,$M$�՚;W��T�mv,f��n6L�/�;��WNB�T,���~�Ӄ�ͷ�������
-А���VE	ظ�h3V�BAN1��k?{"{׮�����_�^<4��OϥBʊG��@T�?����EY�������a�ȈUV�P`c���t�}��nA�6�����߫Qe5��Fi�(G��<��O4�AJ����d��;ë͋S�E�h�ƃ����������f�9�LU@�f �>m+C����?�1-6�R�
-A�U�+4(G˽��,S蟝3P3?��{a���J���y����G�@���`iP?�%%%��R�0�RL�����g(��/w�����sP�����k�!�`�w[-Q,y4�!V���8TB!���"p�����g�W�}������\YR3�Vkm�n4n���Q� &y&��r��͝��[�T��d��:\�Б4̂j��Y� z�7�mM��˝����㓵�8������ޗ�DI�:j��]FHT:1�y#���%������"��Ũom��^��ƤUVQ(����";�ؙb6��r$��Ҋd����|.v4&;�J83B?i��-�js�uu֣QaR0F�D%4�W����_�LW���{�a�pP��
-�
y��}~Ui[��� ,���_Mg��ͽ�E�ɳ��kT"�$�EJ��� 
�dH�E��i{^��\���UH[����§[uCl��
-�b������c���h".lV��y��pݬ�ni����'��f���%��G]<6�_~�U�&`1��O���FSo}甙�k!��ގ*����
���	�e`+��=�Gp��f����M�&G�2��S}��abL>���e>]���Z���(>l hY���
�ay�E�5&�(��f"~5�ɔ���zwF׭T�5����eW�v�ٙ-9��~utF�^HP�f���f�o�A��`����'kQk薵A^vu�M`m����o��
-�������[
-A;s�n��O��'ǭ��qRF̄���q�#�BQ�")��I^�$()�A��ƀ�J��":�b@����-C�-"9��R��%�͒(u��kƻ���4����4����� �/����Q=m��Y���������t~�Wo3
-��j�(��A�v6�JU��ԃ,{d�ԈsDJfc�'�3=7 ���zz!Lժ�4Mr�مu���DP��\H���JBC��*�JG�&�+:�.)�f�lCA�D�D`�E˂�VU�>�� K��T�ry��B(#Cv�������l�/	P=����&;2��赋�߹��X�gctm
��xb�����E�����T�ڱ��\_�
zPU�vG8u�<�œ�5���6D�`خk5�p!g�r=%
-��k�4����)*ªZ\:AD���g%C��c�	��X\�3ZD1s��k��qqs ��I��������#�[��H@UI;��V@��ɳ{�Z�ՠ��Ԑ4� m�I+]W�H��݅1�xRlO�0۫7F]mV�mA:���B� �������u8��m�Zz��� 2* �@0��|z��rL�ZD�!��
w쬘�F����mѕ(��呭W�`~ ��Q���y��5��Ǟ���ҩ_�w4�^����}��bU��L*Da`,B��`ܪ&PQ�l��B4�Pgg�V�YC@&���Ox1���z���h��h=�s�c������u�
=4�A�L��C�m�9��}�o
-i�ei{I�dM\��HYX<��Mi	�[��E�#�h�ya�F�ɩ_�����b�%��MU5Ӊ�7g?���j�-��4;VA�Q�A��q��B�
-L�c;�=��k�ɘ�֕,�=�zrWOE}�'�κ-7̲�4�~4ۓ��":���r�(��X��4R1*��������M��i,jJ�!|��iv��Lo#�ln�Q�p%&�H�M'��2ۖ�3����{�̘��
-�UDt-(h�vk/�4h*�<�܌Bk혤zi��%�����KEA��VJ��!I_c��0*5h�e"'�*�>#�4��@Rɺ��lK����Y�X4�̴mK�����2�_@5�v�Wm+�ʻ1/�.�0C�,ر�*����|���jjݸB$¨�'+"*T{����t��Q=3�^=^�C3���y�󏖈Hp�U���+��g��IW��4�'%g�{��[t���6�3_A!B�����f�9tP�VŲ~�#歌<�����`�$�[8��wҭwn��,�B'�TP�H��q�^��s��7�_lF��Zά"�@$�ň��հ�G^,�_����&�k89�j�����.-$=����(P�L���KRpK!
-�)�r�R;o�=H���$�.KS
-B�$��N�[[0"�R�L&Ųb���.Q��S\�䔗0s�"���~���kK�НNo-��|}�ҥ�aW�Ȩ������ő�7����𿻶XOio��T=���.�"�z*�W^���;K8��Y�/_nFF�e[���_^�Qe�F�gJ���H��`^�X���v{Q�”:�\<�3DN�d%ET�-�`�1����������ҭ����7���_�Þ��pLX������^M�W4���'��<������Mۺ�l�{PB�ꩶ~��}������<�6��О�^u����o6O�_��f֯����cUJZ�Y�?�&s���EUHw�1�*
-z�r�)P����f�U,XR�����g����Hj��A%Fh��Ʉ?9�I�[���&�#��wȺX�o虭�T����A�f��+�\�-@^��g'�6�c
-��ī5��>H+6Ԯ:�G
���>[�YMRY�V���E%�^�ه�Q����*��0"`�ޠ�Pм���t��6�7gk�h��9���,´�1��d080���U��!Pl_al� n��b�����݆ԛ�1�	�r򲿚	��lزc�tZ5��'3������cj}4v���U���18��1*^��'�==�hL�Q���	�>LF{�����cc�‚���2���PY�"'Je���'�R��J��d���z�t�)�NT�S�W��=j��ɾ3�5�i���f'�A_���Z�V����pҪV7v\�����R_O/'��{����kAfEO(�-����f=n\�n����C���z�X�6�����
-�g�p��գ^Ӵ
0�bb�|" r�9GA�HnM�"��[R��r���!��<�HX����P��2��H_5Nu%"!x�\��ۿ1멱F{�8��bы��dygsVN��ߨ�Z�t�F������_��f_n6�I�8
A x��/@{Q��N�y�6WOO��ý���J��j�.u��J"����[�pD����?�uH�Ɏ
x���fH�Q�F�Tv�d�+��������4��hMy�x�S��[&D���"z�w���$@V�h� wZ��O.o__-����x%��,��������d�}������U���@3`d&�* �m��dO�ŷ��ӅpPA+��y��zԣ�"2$�W�j�q~2�|[P�U
-pD�$9����$hBbj��H�)�G���<W�`!�
Ee�ɐā���}�w_3hI��Q��Qoנ@`q^9�I���xt�zoɲ�v���b�ח?������?�7��y��"�x�ƐW	~<F��M��l�T�9p��Z�����+74�$í0����"vx	S*I
,�[J���nBH{�����m�KYj��T���)��Y&�YM�ػ���/Y���R��ۓ@�Ior�~�?|����{��w�IP�g���\����ƻ��Ɵ��c<[|0�Y\
-�)h�;���
��s�� ��<۰����ޢWD���O�/%���Ҵ�0~Q���ܪ�_Q�Ѳ_c�I�9V�4�C�b��
-�LY4���4@T43U(%)2�����( ����3�c�uX�������vF@��w���is���-��ٚ�D���P�������ۯ�x��$��@@f� >�����)���=�ƍQm�?yx��g�����2�H%6Ⱗ5%Hqi�J9n�vW�M���6<�<�q��V�q>:�Ͳ~��8�����bA}�_��7+t@B�h�[N���Qs�(u0ƑY5��zs�/έ����5���7�������w�zPm�%T��Wp���لеWkY��ރ+[�`��ս�8�7'�;d�����">)�*�M����4	�k�[rq#XR��N���	 ��U�VYd�))�"&\�"+ՔJ�g�}����n	�EQe���弞�����@�����B*�2�k���j��a:��F��;'�+��o���}+�%'��
n�U�N[��#�8��]�E����s��{rՕf(�|���t&* TU(p��3#�l�,ř�d��X:?�sJ$��LIlJ5�r��:J�sk>(@�E(R�x��}�����)��<9?��~t4'4��{_�N��ƜK��J�73���uv�/W��_��÷�q��;B�*�HHi�X�_w8�ʚ�����!ˡ�x��,�N���\�8	�
~B�Xc���e�5p�m�"V�ܡOj�
-`ȸ�B��y�媢T���*]!6� �j��9�M�|��?�
��`��B�sz�/;o�H�\���}<kH�[���Ž��)�N��k�j�n�(;_����e�	DU!C���Z����Fҝ|�����HϛNj~��ġHt�-���J%����Q%�D�R�0�U��9�DPE�ټJe����E�4��l�r��ߒEb9?��*�o~�_ϔ
3'b)�X/��u8�HB����i;D[s8Z\��;�+ݭq��ƌ�~L
-կ|��ޫ��UC@�.�:�<��h�����Jm���'�'筨
-p�%nҐ�!��(#E��F���A�~��b�=��y+u�J]�*�0��2�[�l��B��yv"�%J���܏~��s(�<IQ�D't�R{1y�?~��,��;�N?�t;;��ݚ�+f ���t���K�@�V���L���օ����{�`
�P�;v��TC�'����M�4�\E�*%U��1!�DC�ѹ��
-5���XU%�<"@�D!9��ݮ`�L�$�d~� �ȪѼZ8�����[��`N_�&�jc�>�`���=��w���%U]�|��9���w����:��j����u�޻���(N,����d<������c�u�|���R�8hV!G,�|$̒�:|/J��
-��T�cO#w/E	�9�Y��� �"_�������0�Ä
H"v@a��,��[��YLO�_�ׯ3!mɗ�㦗�wF�{�ߣ/����_{�������j~����U�-�j�`�p����7f��w��snV9���`-����7lS��.�_j���EƊ�����N���T��]���,O��AN�s_B�5��(%C�4)�xJ��j�~'M������[�eX嘻����z�'`��"
-��1n������e���zl��=?r��ly5;ܜ��3��L�2�	�>��q���l��
8�\ٚ���O����Ʋ9���"gX4�?�!�>*2$U" �X=�ft�����ɛ�Ԥ��|�c%]�4Z��P�h
-�L&g�EP��)�2-Z4ޫ�7�|�<%QHR���m,];{��4�������i�Z��nV�
TG�
-�]�
V�*x����K@��j��������.C��ѓ
�>X�v{i��� ���`J�b�Nu�o�&����Or�ˊ�n���H���3.a��ƵB���Q�wчxg��o��$h�bL
� "j��w��׼�?���f
H��i�Ke���!�*�=<=�L���!jPQ�Խi�PMО��8lL->���?�8��[	:H�%܊!�*i�xo��nP�\�l�;5��(I�H”f��R�PD�$�&�-��
-�(���H�p��� �
29��[��o�&nP6�z/J}�a��뀎�
d�!{��ٙ\]�Æ���|5�����j�R@厫�+�닇��:a-���g��k�Kc�͇[iKxIe:I��@@� �ի�@��*,����$�skJmg �QIYq"��	X�I�-�B�C�������Fh��׈�#*1��2����A�����P+
"ηph6�C��1�����Aj���
-�0�U��R	� 5w�<Yֲ��1����������$[J�v�"P����4G�(N/��0��<�6����aB&*��$@HG��K�4������&,�k�[ iF���oa=�O�D@!K��,�~��_U��稩�to3�h@%���t�\�"�$�`'��7���=���Nɛ���}v&Y����y�6�
-#ֹPFTI�PA����y	5�ߒ$�Oi>�R)Z0ҥ�+ѪXA��3�
-J��bx�ޒ`��2�"������X�0��ȈȢAD i�J���Ć�3�a_!�w�P`<߬f��o���Zq���0􏟝�N�>���K���`Z#�bV�B�Hi(k��i��#n�Ҹ}~/A�������4���E��+h8~��U���D�fj:�7�*r[���"�xQˈ@2�{kd)���"d�Y@�hǒ�5��! 3��U'ԯ6��A�����R{��2�Nx�����1��^r����X�!)CRA��@>or��q�]&R�r��{������R:h!�������V����w☾��'Op�.V
fQ1Α��A�Ñ5YEQl2���F���w�gX}EQ��ĵ�s����Ts4�O�ZOF\��N�>�bU*���5,��Ϫ�j�<	8���|����mT��+�%t h�dl���ۀ
&��)0�U�j�����v�ф���w�3^�v�.v�an;F��ݺOF�
-|��w��b��fd��E������~e�(�PY�Z��‹m��,l�����
-)ǻ�-�b�&	�@Jq�	"�P�\,�…�OJV�,NJ��`
-XbII�A����r50�[��>�j�`�[�/7�x,Ⱦ�D��.XCkl��k0@u��P����'�e6�ͮ�	U]I�VF����F��l†D��Ó�����T'�w�]���k�io6���9H2A$S��:�
&�
-�i��	O�w�s��~2��"I����E����h�j�(�p�
��<V)j�E�!��j���ӝ��!*aP@F5�V6T5k�H�������Q���#��$j����C諒���ų�J���;���@U-V���G�6 ��M�҆sH�8��	D���bg�e.U���P�s
-�ڗR)�p~i�|
-�Cz�Iy��	�z��9��2{i�����@�	@��A���%(�-��lߝ=�F�٨" &r��u{ ��x`�wλڈ=�5��RG�Un��L'n�	�x�q��Qu"8��"e�I�َ�d�M�d��`�El�8a���)K����ҕ$��**�ĕסZ�lBG"j��U� %����k���+g��.����:aׂi�S*���6���dD*C�.v�V���w�L%�7�ɨ�Ġ;��{@�H{��c�L�˵��}ܣ"J��H+�e1�������0�,�47z�D,#��IJb�33}�@:������!	��yT�!I/��)m�b��j���Wv�	�AA�%֍N|cRئ[ۡkV�/�'��!dV�}͈��;��"TE�۟؍��h�~q��lO���K���
-�e#oKR��9mՄq,=��\I1(oKF�X<�|�!DŽ��h���^������9��h���gE�Aj�{~�����M�X���q�I��0 O*<Y-pZ������;;MS

-
"��R�k/@�d�)��?ݸV?]�O7�hϞl��h�vK��	�B®m;�be�JA(`V��a~���FQLC��!�������H$Cpt�K�DÈ���sT L�e�H�?P����0}�0�b����0��n��l|�5/?o^:h,%�3�7�{�L@e}������ߞuܭ6/��vO5(Ċ�t���6Ꮮ;�J�U�R��I\�8�J)'c���L�$/8 d �DRL��TsB��#��y��01�2f�n�d��$jLj��xR��#A���ΰ������bg�5�: �ǫ7o�9���
-�j�{��@�-��'����Wf���Ɏ�Lt~t]*����x}�.��v�Z(K�;W�|�����B��s�7aN�R^�5��8
K�@�-O��<�Z��fm�lH����R���a|w�Q�@q%0k?!.y�{��u���\n`�r�tm֞��;>v#������>z dw��0��Txw��
-ON;@)�/�a��.��\�E��s�*�7J=�ij��n�"bO0����I��\|^:E!R�U���j�ҹCDP��-@Q`�
-$P�
-`Ռ<�?^���kϓ���{s�?��
MmE%@P����i@���޷��ZmW�6����.�A�@Vʺz�A�?��Т/]�zJD.ѥ��J������Oq+j5�'�bD�5����:+Vl�11V"f;�ٺ��a7����@ƒ1�"1+J73�5�l�?�lcN�zT;9�V,�ţ�eUL>���`��:�z�V�߁Mo`�\�Ѵ>9:�]Z�1`@��9N��'M�%e��BgZJ�%l��@*i�/�����K듐�(���Z�(��|+cGS�+a�U"�0�y�����VPUO���ܣP���y���*\��z��߻==;yCP�n߬m^��E�*�"q����Laԭ��^���]�����<�Z�&��:�����M�a���H�DE�/���)��G6���pd@T��4���ӨQ��E�*���P>Ɯ�X{��T�Ȉ8�1�/��D�Mٹ
�o|�8ҁi	 ��F�N�vkn�������d�QG������t�c:����\ޘL؉�������3���ٓ�Ϯ�D����-�p���)&�E(a�-��b��&��qyP�9��LD�j��D3_ E8�>Wra]�y<S�Q>h�u���}ˑ�Ō�!��	���1�퍯Ofg��U=	�,^�
-���]���h��qkπ*�ˤ=?�\=k�"��g�ِe��3���:�gN�����$DB�Y��n���K0qIJM/C~.�+���	���	�t��Ը�)�����5��'�@�<�����bn�,��i�?;ݩ��[�zB���\r�籮l��٘��U��Ν�{��G�4���A�9�.UQ�$g�Qԭ��W觀��B�=dQZ�*������aNÊ��J�{ђX
-�>��-����iJM\TMH�D+�A���s���B�D�3Q���d<Y,���f�0_.�
-��VǗ���!Hj�k{4���Rk�`$`ߒ���'���{�?>#��ޫ�H���%	`�E`P�B��K�&	�#M�
-��
-A,{�WIb$.s��I�7D2E��|�ڪ��[pTL3ât�y�1�&o��
-lJ�6�mЎ��#q�VĞ/��r�#z�>h��#U}�,3x`��d�<w�=�wg�9��W��J}�\��@��jTL��\@zJQ��h��($�R\!We��Q2���\RI2��"�����������9C�(
qL�h
-JM4)q'<��[w�'Dc
���^��J�s~1�Nft�ZZoD��t�i�DSU��VnfJ�����t�S��x/���%%P�GZ�9e�_�|���t7��zR�)NCCȧ^��t>gD�Z�,*Ih";~��
-�D"6U%&,�2��0@2&%
-�Vo�ک�0���:�`/76��������dҞ��ٷ���9j,
-����z��-����o�$F$��t�	8�+��RA����ӣTak�{�WlK' 8*RA!n#-
-�4�F�a�Q,,��EK�7,t4|Ҝs|t�A-�%�_�7o�^#2,a0V���P��;��#�o.�W��߬�����5%�3�Jk���S��ZLh���^�AA�
]�PM�N�=g"U^J�̩5T���IӍg"��� �3�p�S�+m��G��>�������Z���E��$�G���<�|��ы�L*��8�v��c�
-~d%�w]XZdu>�7w��2|UEP������-��A_�48Fq�4��>Z g5��W���<!Eȃ,$H�i�kL�\�cU�����$Y%���b>�K�O)���$B4�����.@��]�(��~)�QĻ�I�i�N���w-�>��v��wgSEP2�ȪPD��HV{�J�L*�*�
V��r�Kb��B�ϓ{�B�	8,�6#�a�%K|(�����D)]|,�����;+��n�����{M1��dVE�Y�l��{�v}O:D!L9���6�D�y�4�Iش�`�`}�A �����6�ns�d����z$�a�n�#����&LZ*	P��'I�����E�$J�	�[4K�c���Ze��`��C	�3�VW��y>ͶKݺ2��p��TЁ=è��ae������y��1 �WL@�j�+VO�csA:q�j:�#�4M�&�zUЀ���BO{��\�ׄB��l��#���>n�O�g{[�Q*�����'J9c�"�F��_�����RM실�Lel�

SM�~�CUi@$���Ͷ���}��$�P����A��#�H�䲒O���x������.à�f@"!c@ԯt<���!��t}�Z���q�������V�$!,��$�L���ӈ)�4�üFY
M��'@�ԟ��Ѫ%����o�vY;�3���SO4O�A��Gc��y������J�d�e=5�El�B��T�ڝ�s�L *C$���#x�i+$�I%�vv�f�33���+L���4,��@�ؚ�l%�HĪ�lN�S4��j�j� �r��rDn��[�⑓���1�1V>������]��@F�
-
-Ou������[q����h�U�rD��;����茎 �`�S�y��X<1�8���@J�Ҏ��^(=G�X�Ec��ʴ��i��ݟ�3<��2�)�b3���#rA5�.D`o�%����&��{*�"�o`F+���A�hp�B�K��<T`��n��>�V���=���4c^��:T[A��>H׊�I�I�	�ۺ����n=
�Cc�).�F4��˫��Nh(5�>/xg��V�ImL(<�B�H���f���%�K#�F7�<���T�����@4t���2"B���$��o�΂!J,���h݄��ٜ[��m�*j�� �/[d�Zj
-���;��M̩1�¶bNs10f�ha��CbH��V˿[���uB�*��R*|4�������w��n��E�A.r��)��
��#o�U�L x�1V
-j��i�z�;h�_���8�lmP��&��*z�I��K`$
-D��u��C@�|d�"AebWSs?�낥!C%(�Z0�ԗ1��C:���se�m�	ş#�ڸ��Ϧ��Jd���Y�A
-�Ĕ	!?�y��n������
-���\�*r�'�;���w��޻1��;Og�AA��O.o5�{�:�HB�{�L�:0(V�"��1R�6Y-��*x���$=_��K�����8�uE^7����Vgӛ��JA��F��#�b�K�WVy��2�ͼAh��		r��j����	���V��_Y��������?"*^Z\�̐�"=�����v.Ԍ��@�4Y�2nmA��(W>o�
"���� P)tb��MHK;I�W;R@��JG`�3g�\��Im��ֹ���M��?Ā�!�Ĥ�Wd('�����n�-G�|�?��y2�A�3���ʢ�3I =M�T�����k�9 c�V	4���ޤZY��(��I",oy"�f�
-��ܲ�P��,~+��g��NIn߷q��Cu3����\���(�wa$Q�iЯ�����ض�JC��//��qөt�0���kUz��)�1����<O��{�J����D�:����o��xQ��P��t�U������Us�ҥz��'͠�0�5C;�2�<�1�P�,�����d�BI)��Y� ����FO�t��w�~}%��s�c��f��q- �0J�Ct�Lk�a
����j^�ފ�6%����V�d�s��<������TC)6��h%9P��Jސ0����BJK�#{�,�7^���_��I:jK��81�6�N�2�T���t���r
-�²~�əV�_̚�B�
-����
���nj�W��k>�˧֬��Ce ����q���QJ�Mqc�Y�1JTma�5r0�bs��k�G�bfTe�⺐>2p����Es�D�,RR�������]�����se�]""᥽�"ԓU75ݣ�Z��������ߚ������a�u�~ٍ�{����
95���2�+Tݬ���f*hF�����"�U/�����̨f�8��4�|nn������a��\��x$�jd4�ӱ�܀��7��v��&n��P�D�W¡�$��l=	�_s���6X�-kC���ޏo\�Wk��$hHD�R��fp|(��Y�M�uSk늃���Ɣ�&��|�Ǻ)��	@�Bc�2infR,S�,xIJiW�Ds���H���nA�p�	�&�[�"�Z�ۤBL�P
-Ȃ��;Ǘ�Jۣ_�F*����u*Ktk7�qج�����k�����L����whm����j���wB8�L����%h��!�F/�D���kd����|��"�����A��Y��%oh��������<�Ϛ���|��x�I}ł�d��𜴗q,�\<[Y(֨8���M�]w8��UY%k�t�?_V�\X4�"3�@kd"��9@�d�G����_$�xy+�-�ƅA�	4�Iq�0�e��1iL[p��P����h!��r���U�G�?�zi�Bh=P
���B�D����u�nŏ��}�{�������9T
Jb+�tv��*�v쬂�
 �6�B�G$P��r\�<�$�x���=�0V��X��I��C�#]�m��+/�ټ�T�y'��С�K*�s�2��|U�d�>��_��>J#*R.�D�"͊B�8�ruR�z�=Ӫk=��v0ڹ��a�Z�����a�+'ٝ�̛�[�J.�7����**��j�&2�#R��Y+C�{4%qJ�XE��d�Q��X����i�@����uΐ_U�/���Yة�SE5;٠-��~�1����H��6h@�������+��5��Zx�~����;����BC�����G�IhP�}��!�E�Ւ<cl����?�2�`ca������E_Q�|�Fh�J\��F�b����l�Es=��X�jsF?�O5%V��.}P3P�(�r`��a�����G!�U��J�^�z��hd���]+��
-������5��o&�l�;3����vˀ������v�Ȅ���Ũ���]o�|hR�'6��h6DI��e���T�
�aׄ�VIٷ*��+H�Ga9��_�o2�z�C*E�}���?�ԦĠ�Z�z���z���܄��f2�ɍ�x�zU��`���riF�K�~��3���ࡪ�j��Rm@TB�U@A��H:������Є@�YFR�B��oB��v����S���S�\Ojԃ��C/��#�ܦ]P�w��RY	�4$��ED5#P�qMUH�Re���R=���tْn��kuP_�5�*ˍ��B��A��'M�@b������_��
-:E�Iŷ���ER9�� � Q[J9�s\�-J~�����L����qBf�c�4���뀍�l/�����T��G($@~�ki�RqcT�X=2T`
-*�w�Ʒͣ���h�ye��`��0�s����rF{PM��6
#x0���_՞]��Y<	0I
�nwx�s'��*�.Tn-v�����(�,w��q�*I>�Ld���ePP1D�s�E�$�[.�R�XC齍ځ��zUC�bH�64��e��:�ΨwJ�a������(��y[a�X��NGt� � �
-�O��Q��xb<4
�(���V�K�O�� H|*�9�9=�(�(��ȃ����%Z���)����R:R�LȜb�WV��i�{���F���4�xb
AP�A���<[��r�i�8o6Wq4���X�|��I��dq�u�X��|>2��z\c kTHdz�����m���N-���:�M���M�3G����4�J��QA����[��\�p��oqC�2�iX�*Ƃ (���n�C�3 >=�"EO��y�2�X����w�ek��6c�*�ϕ�����(T��˕��������b#���1��+�A�R�^T$웩��n�";{�2[�m���*�U�W5�M�e(���B���كJb��܎L����+���+R���pr��5��1��	�x��悩���~�
-�;�,������s�4D�4��s�Uߝ�]��`U���ܢ[���@l$(�/;C=�j�,���0�����'���<�ҹ�[�c�nqpt��*�jA�n��3�(+_j�|�nӃT�T��A������	C��$y#�N5��� ��4FŻ��r�-���ý�;bSC5�N��*���bLՌj�v�1V�@�k�0��X��ldۀ���0P��R���ݖ��F��H��[.��7���)v���#�*î��*n�(`C�(��&����>���o؛*4����B�Ud5��
��i[�+3��ss8q��v�C��z����2�3"x	�*jfS�Vų%�����Xf���~^�+ !�8���c����9/�ٰ�%*�:>41�$���ǼdzX�ѡk��ܑ?b$�"o�O��:/�� �u��^�҄W8�L�,֣k�������
��iF�����v���j�u��c��Ql"B ]x�0H�"a:�B��e�d�%��o��"C��ʔU#�I�U�R�%�WL�i!j�G�����[.��Jʇ�-�B�3�Q���*B��U�����=5<-qf*���'0�u
cwr��f\��d>�Q�
ԣ�-����:�@����lĨX
�	L"C)����ݢ)�j(�W"�1a��@�fs�aM�"W�W�!?(��|2
<7�2}N"�Ȗ/r����u-}�*BL�8Ǧ�N�34&,��mf�oߺ��>tG���H��4
�[���X��Xû��Cߣ!��A@�=	��5EŤ�.��%���
7�l��1�+I��82���b�=矷�,OHQ�@��M����:�¦���W:�b��:�(�\r�DBL\�6/��'�9�4��_�5�P��O�aD�v�����55��5����̈́�bH= ZF��2���)U��;s%X#h�.UJY��"Ѣ��m�<�U�y�b
-����3_i[YW�]�|�g�ðD
�ܻ-]iU�y0�A���Ad0�w��s�i��;���ۇc�au��C�tjL5�Lg#�֫�lA�{Tq��֚k	"���@X�#�LbҜ���]#�QҚ`�R���.����A�9���
-��lZ��B�nKˮ�mU���� ����&�1�LAA�� �ZK*Grه��z����L�5�[}|�NW��f<7�QX��wҰ��@���ړSfr}� ��b�B����H�+l���UK����	Z٫�,~IcK�]�����	�,%��儅�K���cV0��K���So��1����I�+2�TD0��~��3�a��Ѹ2p��ӣ=x�;���cj[�k0
�e$ ���o!�4��2�	Xf�y���!JS�ɡ��������r�j\��v�C��v[��(��F"X�1
-T���fO�����ql��
0�zT
�A$t�說�6�i%��^7�1�����zu��n2��Ov��s��~\! ��{Qn8���:�2!2*ZKڵ����%�N���ɕ����f		(��
-�>�Us6����E�[Kv"��QףD�k�h{$Ɯ,�IW(/t:a""0%b��x $E=��v*��tg��LJ�x�y?[^.\��to2���4l�>�ؐ
-�>���Wk�A�Y���B+q���'�x+R�5��p8�`rEɞ�[Ya�>�����K�i�1BI$���"��ͺ�Z�,��	)�A��<  ����<y�:�z��GG�{t�Њ�f4i���'�ʃm�IU������վs"lI�~��̺���T��y��~�
-�YCIG�w5&,P�|A��d�vY[M�_|�s3H��LP�,A����l��j�4[��}��jj
�����cV�.����T��`�1G�_�g���H�[�$$bU��� ��=�	��La�,�ۈ��A����5������2���,Ş"
{qZ���ù�\�u4Jz�a��W�����ԪQ,@�rJg�(#��[��*���P�P%����������ȻU�x��lM�F;���dd0��<�"�W�p*@Ii�û�!�j��� ڵ:��fNm��$J;JZ�X�(}�mB	B��,q��g���X��d3$8b3<l�_�{�STA
�m������A	��<)2��H���V#��w��Z������+���F�twg6���]���yS�B��+�:AT���8��M7UѮG�!����)��{����j~�9Eύcp;�A)[���"H���!�0�Th��F
-�4��UZd�7���5�
!�pU����ޠ 9��
�ݜ�����٧oL3��L�f:"/�eۍF�j��Whm3�6H��M �l�X���ׁ��I�hK�%j��I���)�JiY�CE�.�墏a	�u��'d��)�xq�,�eT��Wk�'~!ʤ��S5"b�А[I@1t�ڮ1��@�G�����U�6�f�sm:o���:5`�jbT%����1u���A�jx�i��oR�]@(n�����L-�6M�_���BJ<�\�i�d���I�o�Ƞ5��L���(�P<�1sj*꒠�Am}@d�$�U�AT|]��Q�';{��w��O���S�G����l<�Q}�J��j�`�@F@�Z(LN}@��y?e$��eCa��oWš
�,	eLLWA-�����e0CD�R�s��ֶF���t0F�J\pRU.���z6��EZ�3xbLM^�	D�D�=L���WXcw�^-�6m�	���|go4ߛ�)8ׯ�I�X�14�Ip��{����޺ŕ1֐��c��B[*��ʹW�y�,:��ҭ��z�p=��nNWH5Lh&���Ly�+���ï�hKbor����f�zb���2@ /�l���"zTv�n��;�C��6ӝ�d���S��ηK�aLL���5�gcT��^<���5aṈ���I_GoL=g���r$ebq��b��qw����G��)�/����
-F�L�WZhHF.�ϽCNZ�<���,ai�\��!U�B	*m����a������I�P�R�
-��*V�,�5$ݚ�q���<�*>�e&�<�+ݸ�y�Jz������P��r���G)����'/�<�t������IEND�B`�
\ No newline at end of file
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-2.jpg b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-2.jpg
deleted file mode 100644
index ace07d078a00bee3b0ab02f7f077e39c23f2aa03..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-2.jpg
+++ /dev/null
@@ -1,19 +0,0 @@
-�����JFIF��H�H�����C�
-
-
-		
-%# , #&')*)-0-(0%()(���C
-
-
-
-(((((((((((((((((((((((((((((((((((((((((((((((((((����<�P"��������������������8�������!1AQaq"��#2B��4��Cb������������������������#����������!1"A4a������?��E
|�U�Sa��z��>4��~�L�S ��A:!��b\�%g8��s�ЬT�f��`8S�Wǂ�ˆ��������<���utF# ��]��i�B��DOo�u���[۸��PJ��O���]$� Ԣk��"ʢ�{��'�C=;l�l�cS�<D��ᩧ�w����Q��[L�6B��ߧ\������=���C
,Q2�#*���d�@qI��2�^%���
-<��k	�w$9i�Ǔ ��&�—;`穦!OL�X🇯�����"�nC�a��0�S��I0$��ǖ�$���9��B�ƹI#�q�e<��:�rHA����D���|��gG�L��݌TU:*�gm����2Z�i�͘���(9�*g>#�:���!1�r�O�p1�����f��A�w�f���µr��˶�䒖Z@�Ȏ�:��۪�ᖤB�9I\��Q�b�MC� ������Yq�aq��~��*1�@�����jϳzhe�y�cbe ����.ج�Z���i�Ҹ0� �H鷎�{?5��JH��3�#r��$�N@�Da�t��q,pF�
K�>2Ű3�2z�A�p���@i�DU.�
-���}��x��P�5����<��a���ּmn���#�V^��(�V�C���u��5��QCU�������>Dz��s!�Ps�N;�k�m|�ƅcs&�V���I:����%92��!|F"p]0�Gj������UφF?}�c�����tQ�5Tj
- ��Ơ�������U������:�c������Qs�����b����]�����<UT�Q.-��S����̠��q�>������':u�0�M
-p�E��&��NDgf_oN��������Z�T�Gީif}�Q�����].�")̢�����|Ct	��t;���m�}|Έ8�)Yi+bh�n�A�d�|ƌ��a�����d<�/9f�R?s��K�^���%��B���S	\r�tt5�+�(���x1kl��Lr\YMi�
-vu�pA<��z�X��j�j�'�$.1��Ч_/u�xk֖3�n2GQ�7	.y�z�TF�rD�3�
l�}B���D���Mw���������ƚ}�[ym�Q$�Q�H� ����F�7 �`�ȗ�X@�ӂH�3�~����Q>A���J�����]
-� c�軳�����S6?ԺCԿ�
-t�I�p�:��f�R��A�-����\�U�AS$�?v��2�g��Fq��;�������t�nwo�����
zDNVU.�Ĝ�w
-�}��l.����~�$��RV�0��#n޸���3�7;|7\��
-�R+x��i���K��utEdn�9x4sOc� f�|��_�~GF2TP<�[o������� ��zx�"�2�]䨅�,S�S�ͦgg�5–��EtIp��]�#؝0On��UUwo�1��w���G�.~l��
-����D�
%$�G<��y�P��җ��۶�����
\ No newline at end of file
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.gif b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.gif
deleted file mode 100644
index 432990b832d877e8dcdd9197e39184be16900e50..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.gif
+++ /dev/null
@@ -1,2 +0,0 @@
-GIF89a(����������������������������!����,����(���|8��0��
l��ݵ�ҍԇ�d
-�gp,�E��
T�l
����{�Q���1�����|�ĩ���^ޮ`L0er�^���`�����7��G�{vnoyi�V4�0zk*$����a�����r	�;
\ No newline at end of file
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.jpg b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.jpg
deleted file mode 100644
index de4eace04ecaea499e70812df0797318d700ad9f..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.jpg
+++ /dev/null
@@ -1,10 +0,0 @@
-�����JFIF��H�H�����C����C�����(������������������	�
-���1��	�������	�
-x�6789uw������������������	�
-���3���������	8w�5u��
-67vx������?�BsO���f���,��ҁ��\C���������Y�_���ԏ��ĬJ'����f)��d8E��v�#[�nz]w.�m��t6l���-�=���O��0G7!�S�B�@�$��M��mI��Ӹ)L���������&V�"U+F^`Z3p�0C�0�ሄ���O��|�N�ӼA�� �j�T�{�sQ�M�LG�N{~F�NW#BaE����)Pƨ��Oc��֍8r>'���6�!��2)��n�]ۈ��f^�gQ��������W�8�/R�@v��~�v���ָ�ݚ&vv�T���XV_fgǴ��π{��S���I�{�5,�ϞJ���-�Ŝ�r^rM9��<�ɳm{ΣJ���k�\R��fa�̷�����L��Zf� d�=E���A+�H��Y�I�Zp)@bVm�:H��4�YD5�)pQj���e�������Q�ɑ�nH�K�759�iE�:���\HwL�j@n'�����%�`�o7
�h[��
-�(ihi�-�(O-D҅�n(ZY
8���ߓ	�?$f
Yg�yjB$�,u����wi��-0O��HF�{��J���߬�\��e~��U?}>��q¾�c�	���>	��{C�G�Y[��!���ن^�H�:I�a��"�t�$͑3��l���*�k�뉓\]�=�=k4��ҶGt|J�V��:b@�6�$d�Dg�<ҳ�J����4����_u���3������DBK}A���Ms;�2��4z�1�SjQ)<�B5)�ߘ��1��KvQn$����7#�_�$�"�Me�)U����!�U��%e
TJ�Oq��.`؉��z�/Y����0_^[�LL�wc�?�EV؊tG
-�c?u./�:x5�T3�C�f�;��\�Ed�9*]P5�G6�\g�v�1�2��׷(��ҧ5-��@Ա���'��� ��V̅F�
-ÂQ���I��c���n2�=�}��?��>����$�r�%�N����W�\�Dڶ���y�j�M���G���л��}�����۟��T��~k����
-7�;8�0��܏���i��D�t�}L�\���~����H�a$F��:�\�?�EF5��d����m�ͷM��QZ|Z�/��,3��U��_t�����Bnr���K��ԦE�q����>VT��)c�h�%4��ԕQr������힦1�,]A��;A[%�"aUVGWڅQW���ݍQ%��gkиca���2�ӤL�	W��I�j�-�C��-�-���5/�-Pޥk��੩��ո \�!13RB0,���S�@+Fp��"�C�
�GL�Ŀ�6�LF�a���"�z}=)$��A�l��!��ԝ4d��y�L5U�SY��ޕUz�R��P�g���}5��9�����5�|��b�rI3���W�Y[w'�%�v�v���y�v�IRQ���>�I4�e${�zש�B��Sj�]�/^�N�LBB3՞i�)�%9]݄�Ya0�?��c��۱�
-�듽�|��O�F���:w���<�T��C��G��t6u:�N�S�і������#���m���Vm���u�l}�m��W՝���
\ No newline at end of file
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.png b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.png
deleted file mode 100644
index 39c041927e1d7e1ee2b7904763c78d7c9d5caaab..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/core/tests/fixtures/files/image-test.png
+++ /dev/null
@@ -1,4 +0,0 @@
-�PNG
-
-���
IHDR���(�������F����DIDATx���1
-�@������z�4� d%�0y���Rtֆ���@�7���[ �x8�p�@��y����a�����IEND�B`�
\ No newline at end of file
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/test.txt b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/test.txt
new file mode 100644
index 0000000000000000000000000000000000000000..573541ac9702dd3969c9bc859d2b91ec1f7e6e56
--- /dev/null
+++ b/core/modules/forum/tests/src/Functional/migrate_drupal/d6/files/test.txt
@@ -0,0 +1 @@
+0
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/NoMultilingualReviewPageTest.php b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/NoMultilingualReviewPageTest.php
index 25d84c311ec58faae3df5c979625672d88bcf612..e541601cbe6722d286f7454bfdf1f7530529fbf3 100644
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/NoMultilingualReviewPageTest.php
+++ b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/NoMultilingualReviewPageTest.php
@@ -4,8 +4,6 @@
 
 use Drupal\Tests\migrate_drupal_ui\Functional\NoMultilingualReviewPageTestBase;
 
-// cspell:ignore Filefield Multiupload Imagefield
-
 /**
  * Tests Drupal 7 upgrade without translations.
  *
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/Upgrade7Test.php b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/Upgrade7Test.php
index a7430cb32d60db43dd27a9996eaa27102d724972..df6180b495d451e452ab66f6eaeffba336e1c917 100644
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/Upgrade7Test.php
+++ b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/Upgrade7Test.php
@@ -4,8 +4,6 @@
 
 use Drupal\Tests\migrate_drupal_ui\Functional\MigrateUpgradeExecuteTestBase;
 
-// cspell:ignore Filefield Multiupload Imagefield
-
 /**
  * Tests Drupal 7 upgrade using the migrate UI.
  *
@@ -61,39 +59,39 @@ protected function getSourceBasePath() {
    */
   protected function getEntityCounts() {
     return [
+      'action' => 27,
+      'base_field_override' => 3,
       'block' => 26,
       'block_content' => 1,
       'block_content_type' => 1,
       'comment' => 0,
-      'comment_type' => 5,
+      'comment_type' => 7,
       'contact_form' => 2,
       'contact_message' => 0,
+      'date_format' => 12,
       'editor' => 2,
-      'field_config' => 20,
-      'field_storage_config' => 14,
-      'file' => 2,
+      'entity_form_display' => 16,
+      'entity_form_mode' => 1,
+      'entity_view_display' => 24,
+      'entity_view_mode' => 11,
+      'field_config' => 26,
+      'field_storage_config' => 16,
+      'file' => 1,
       'filter_format' => 7,
       'image_style' => 7,
+      'menu' => 5,
+      'menu_link_content' => 3,
       'node' => 2,
-      'node_type' => 4,
+      'node_type' => 6,
+      'path_alias' => 1,
       'search_page' => 3,
       'shortcut' => 2,
       'shortcut_set' => 1,
-      'action' => 27,
-      'menu' => 5,
       'taxonomy_term' => 6,
       'taxonomy_vocabulary' => 2,
-      'path_alias' => 1,
       'user' => 4,
       'user_role' => 4,
-      'menu_link_content' => 3,
       'view' => 14,
-      'date_format' => 12,
-      'entity_form_display' => 12,
-      'entity_form_mode' => 1,
-      'entity_view_display' => 18,
-      'entity_view_mode' => 11,
-      'base_field_override' => 3,
     ];
   }
 
@@ -101,15 +99,7 @@ protected function getEntityCounts() {
    * {@inheritdoc}
    */
   protected function getEntityCountsIncremental() {
-    $counts = $this->getEntityCounts();
-    $counts['block_content'] = 2;
-    $counts['comment'] = 5;
-    $counts['file'] = 4;
-    $counts['menu_link_content'] = 13;
-    $counts['node'] = 8;
-    $counts['taxonomy_term'] = 26;
-    $counts['user'] = 5;
-    return $counts;
+    return [];
   }
 
   /**
@@ -148,6 +138,12 @@ protected function getAvailablePaths() {
   protected function getMissingPaths() {
     return [
       'Locale',
+      'Entity Translation',
+      'Internationalization',
+      'String translation',
+      'Taxonomy translation',
+      'Translation sets',
+      'Variable',
     ];
   }
 
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/files/cube.jpeg b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/files/cube.jpeg
deleted file mode 100644
index 652a7db77d5e3489745effa930f1a41b2d1ba69f..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/files/cube.jpeg
+++ /dev/null
@@ -1 +0,0 @@
-********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
\ No newline at end of file
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/files/ds9.txt b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/files/ds9.txt
deleted file mode 100644
index 6a7e452749cf08168239c3e267a7e1d07c49ddc0..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/files/ds9.txt
+++ /dev/null
@@ -1 +0,0 @@
-***
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/private/Babylon5.txt b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/private/Babylon5.txt
deleted file mode 100644
index 6a7e452749cf08168239c3e267a7e1d07c49ddc0..0000000000000000000000000000000000000000
--- a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/sites/default/private/Babylon5.txt
+++ /dev/null
@@ -1 +0,0 @@
-***
diff --git a/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/test.txt b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/test.txt
new file mode 100644
index 0000000000000000000000000000000000000000..573541ac9702dd3969c9bc859d2b91ec1f7e6e56
--- /dev/null
+++ b/core/modules/forum/tests/src/Functional/migrate_drupal/d7/files/test.txt
@@ -0,0 +1 @@
+0
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/MigrateTestTrait.php b/core/modules/forum/tests/src/Kernel/Migrate/MigrateTestTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..0e2f77c4fcf499223f37124114649d6b6f5f9bd0
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/MigrateTestTrait.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate;
+
+use Drupal\taxonomy\Entity\Term;
+use Drupal\taxonomy\TermInterface;
+
+/**
+ * Common assertions for migration tests.
+ */
+trait MigrateTestTrait {
+
+  /**
+   * The cached taxonomy tree items, keyed by vid and tid.
+   *
+   * @var array
+   */
+  protected $treeData = [];
+
+  /**
+   * Validate a migrated term contains the expected values.
+   *
+   * @param int $id
+   *   Entity ID to load and check.
+   * @param string $expected_language
+   *   The language code for this term.
+   * @param $expected_label
+   *   The label the migrated entity should have.
+   * @param $expected_vid
+   *   The parent vocabulary the migrated entity should have.
+   * @param string|null $expected_description
+   *   The description the migrated entity should have.
+   * @param string|null $expected_format
+   *   The format the migrated entity should have.
+   * @param int $expected_weight
+   *   The weight the migrated entity should have.
+   * @param array $expected_parents
+   *   The parent terms the migrated entity should have.
+   * @param int|null $expected_container_flag
+   *   The term should be a container entity.
+   *
+   * @internal
+   */
+  protected function assertEntity(int $id, string $expected_language, string $expected_label, string $expected_vid, ?string $expected_description = '', ?string $expected_format = NULL, int $expected_weight = 0, array $expected_parents = [], int|null $expected_container_flag = NULL): void {
+    /** @var \Drupal\taxonomy\TermInterface $entity */
+    $entity = Term::load($id);
+    $this->assertInstanceOf(TermInterface::class, $entity);
+    $this->assertSame($expected_language, $entity->language()->getId());
+    $this->assertEquals($expected_label, $entity->label());
+    $this->assertEquals($expected_vid, $entity->bundle());
+    $this->assertEquals($expected_description, $entity->getDescription());
+    $this->assertEquals($expected_format, $entity->getFormat());
+    $this->assertEquals($expected_weight, $entity->getWeight());
+    $this->assertEquals($expected_parents, array_column($entity->get('parent')
+      ->getValue(), 'target_id'));
+    $this->assertHierarchy($expected_vid, $id, $expected_parents);
+    if (isset($expected_container_flag)) {
+      $this->assertEquals($expected_container_flag, $entity->forum_container->value);
+    }
+  }
+
+  /**
+   * Retrieves the parent term IDs for a given term.
+   *
+   * @param $tid
+   *   ID of the term to check.
+   *
+   * @return array
+   *   List of parent term IDs.
+   */
+  protected function getParentIDs($tid) {
+    return array_keys(\Drupal::entityTypeManager()
+      ->getStorage('taxonomy_term')
+      ->loadParents($tid));
+  }
+
+  /**
+   * Assert that a term is present in the tree storage, with the right parents.
+   *
+   * @param string $vid
+   *   Vocabulary ID.
+   * @param int $tid
+   *   ID of the term to check.
+   * @param array $parent_ids
+   *   The expected parent term IDs.
+   */
+  protected function assertHierarchy(string $vid, int $tid, array $parent_ids): void {
+    if (!isset($this->treeData[$vid])) {
+      $tree = \Drupal::entityTypeManager()
+        ->getStorage('taxonomy_term')
+        ->loadTree($vid);
+      $this->treeData[$vid] = [];
+      foreach ($tree as $item) {
+        $this->treeData[$vid][$item->tid] = $item;
+      }
+    }
+
+    $this->assertArrayHasKey($tid, $this->treeData[$vid], "Term $tid exists in taxonomy tree");
+    $term = $this->treeData[$vid][$tid];
+    $this->assertEquals($parent_ids, $term->parents, "Term $tid has correct parents in taxonomy tree");
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..077e94208fcac0278ad76a8e297a86f7f3a0ad2d
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d6;
+
+use Drupal\Tests\forum\Kernel\Migrate\MigrateTestTrait;
+use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
+
+/**
+ * Test migration of forum taxonomy terms.
+ *
+ * @group forum
+ */
+class MigrateTaxonomyTermTest extends MigrateDrupal6TestBase {
+
+  use MigrateTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['taxonomy', 'comment', 'forum', 'menu_ui'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->installEntitySchema('taxonomy_term');
+    $this->installConfig('forum');
+    $this->executeMigrations(['d6_taxonomy_vocabulary', 'd6_taxonomy_term']);
+  }
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal6.php';
+  }
+
+  /**
+   * Assert the forum taxonomy terms.
+   */
+  public function testTaxonomyTerms() {
+    $this->assertEntity(8, 'en', 'General discussion', 'forums', '', NULL, 2, ['0'], 0);
+    $this->assertEntity(9, 'en', 'Earth', 'forums', '', NULL, 0, ['0'], 1);
+    $this->assertEntity(10, 'en', 'Birds', 'forums', '', NULL, 0, ['9'], 0);
+    $this->assertEntity(11, 'en', 'Oak', 'trees', '', NULL, 0, ['0'], NULL);
+    $this->assertEntity(12, 'en', 'Ash', 'trees', '', NULL, 0, ['0'], NULL);
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateTaxonomyVocabularyTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateTaxonomyVocabularyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..13b8e344c739c5b035bf94b2df10a42dd150b7fd
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateTaxonomyVocabularyTest.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d6;
+
+use Drupal\Tests\taxonomy\Kernel\Migrate\d6\MigrateTaxonomyVocabularyTest as TaxonomyVocabularyTest;
+use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\taxonomy\VocabularyInterface;
+
+/**
+ * Migrate forum vocabulary to taxonomy.vocabulary.*.yml.
+ *
+ * @group forum
+ */
+class MigrateTaxonomyVocabularyTest extends TaxonomyVocabularyTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'comment',
+    'forum',
+  ];
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal6.php';
+  }
+
+  /**
+   * Validate a migrated vocabulary contains the expected values.
+   *
+   * @param string $id
+   *   Entity ID to load and check.
+   * @param $expected_label
+   *   The label the migrated entity should have.
+   * @param $expected_description
+   *   The description the migrated entity should have.
+   * @param $expected_weight
+   *   The weight the migrated entity should have.
+   *
+   * @internal
+   */
+  protected function assertEntity(string $id, string $expected_label, string $expected_description, int $expected_weight): void {
+    /** @var \Drupal\taxonomy\VocabularyInterface $entity */
+    $entity = Vocabulary::load($id);
+    $this->assertInstanceOf(VocabularyInterface::class, $entity);
+    $this->assertSame($expected_label, $entity->label());
+    $this->assertSame($expected_description, $entity->getDescription());
+    $this->assertSame($expected_weight, (int) $entity->get('weight'));
+  }
+
+  /**
+   * Tests the Drupal 6 taxonomy vocabularies migration.
+   */
+  public function testTaxonomyVocabulary() {
+    $this->assertEntity('forums', 'Forums', '', 0);
+    $this->assertEntity('trees', 'Trees', 'A list of trees.', 0);
+    $this->assertEntity('freetags', 'FreeTags', '', 0);
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyEntityDisplayTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyEntityDisplayTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2723bd622f19a65a55109ea177d0bfc46d4ca97c
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyEntityDisplayTest.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d6;
+
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
+
+/**
+ * Vocabulary entity display migration.
+ *
+ * @group forum
+ */
+class MigrateVocabularyEntityDisplayTest extends MigrateDrupal6TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'field',
+    'comment',
+    'forum',
+    'taxonomy',
+    'menu_ui',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    // Execute Dependency Migrations.
+    $this->migrateContentTypes();
+    $this->installEntitySchema('taxonomy_term');
+    $this->executeMigrations([
+      'd6_node_type',
+      'd6_taxonomy_vocabulary',
+      'd6_vocabulary_field',
+      'd6_vocabulary_field_instance',
+      'd6_vocabulary_entity_display',
+    ]);
+  }
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal6.php';
+  }
+
+  /**
+   * Tests the Drupal 6 vocabulary-node type association to Drupal 8 migration.
+   */
+  public function testVocabularyEntityDisplay() {
+    $this->assertEntity('node.forum.default');
+    $this->assertComponent('node.forum.default', 'taxonomy_forums', 'entity_reference_label', 'hidden', 20);
+    $this->assertComponent('node.forum.default', 'field_trees', 'entity_reference_label', 'hidden', 20);
+    $this->assertComponent('node.forum.default', 'field_freetags', 'entity_reference_label', 'hidden', 20);
+  }
+
+  /**
+   * Asserts various aspects of a view display.
+   *
+   * @param string $id
+   *   The view display ID.
+   *
+   * @internal
+   */
+  protected function assertEntity(string $id): void {
+    $display = EntityViewDisplay::load($id);
+    $this->assertInstanceOf(EntityViewDisplayInterface::class, $display);
+  }
+
+  /**
+   * Asserts various aspects of a particular component of a view display.
+   *
+   * @param string $display_id
+   *   The view display ID.
+   * @param string $component_id
+   *   The component ID.
+   * @param string $type
+   *   The expected component type (formatter plugin ID).
+   * @param string $label
+   *   The expected label of the component.
+   * @param int $weight
+   *   The expected weight of the component.
+   *
+   * @internal
+   */
+  protected function assertComponent(string $display_id, string $component_id, string $type, string $label, int $weight): void {
+    $component = EntityViewDisplay::load($display_id)->getComponent($component_id);
+    $this->assertIsArray($component);
+    $this->assertSame($type, $component['type']);
+    $this->assertSame($label, $component['label']);
+    $this->assertSame($weight, $component['weight']);
+  }
+
+  /**
+   * Asserts that a particular component is NOT included in a display.
+   *
+   * @param string $display_id
+   *   The display ID.
+   * @param string $component_id
+   *   The component ID.
+   *
+   * @internal
+   */
+  protected function assertComponentNotExists(string $display_id, string $component_id): void {
+    $component = EntityViewDisplay::load($display_id)->getComponent($component_id);
+    $this->assertNull($component);
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyEntityFormDisplayTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyEntityFormDisplayTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf5c02181208aef9aeaabb7e591f11f7ce60acb6
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyEntityFormDisplayTest.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d6;
+
+use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
+
+/**
+ * Vocabulary entity form display migration.
+ *
+ * @group forum
+ */
+class MigrateVocabularyEntityFormDisplayTest extends MigrateDrupal6TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['comment', 'forum', 'taxonomy', 'menu_ui'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    // Execute Dependency Migrations.
+    $this->migrateContentTypes();
+    $this->installEntitySchema('taxonomy_term');
+    $this->executeMigrations([
+      'd6_taxonomy_vocabulary',
+      'd6_vocabulary_field',
+      'd6_vocabulary_field_instance',
+      'd6_vocabulary_entity_display',
+      'd6_vocabulary_entity_form_display',
+    ]);
+  }
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal6.php';
+  }
+
+  /**
+   * Tests the Drupal 6 vocabulary-node type association to Drupal 8 migration.
+   */
+  public function testVocabularyEntityFormDisplay() {
+    $this->assertEntity('node.forum.default', 'node', 'forum');
+    $this->assertComponent('node.forum.default', 'taxonomy_forums', 'options_select', 20);
+    $this->assertComponent('node.forum.default', 'field_trees', 'options_select', 20);
+    $this->assertComponent('node.forum.default', 'field_freetags', 'entity_reference_autocomplete_tags', 20);
+  }
+
+  /**
+   * Asserts various aspects of a form display entity.
+   *
+   * @param string $id
+   *   The entity ID.
+   * @param string $expected_entity_type
+   *   The expected entity type to which the display settings are attached.
+   * @param string $expected_bundle
+   *   The expected bundle to which the display settings are attached.
+   *
+   * @internal
+   */
+  protected function assertEntity(string $id, string $expected_entity_type, string $expected_bundle): void {
+    /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity */
+    $entity = EntityFormDisplay::load($id);
+    $this->assertInstanceOf(EntityFormDisplayInterface::class, $entity);
+    $this->assertSame($expected_entity_type, $entity->getTargetEntityTypeId());
+    $this->assertSame($expected_bundle, $entity->getTargetBundle());
+  }
+
+  /**
+   * Asserts various aspects of a particular component of a form display.
+   *
+   * @param string $display_id
+   *   The form display ID.
+   * @param string $component_id
+   *   The component ID.
+   * @param string $widget_type
+   *   The expected widget type.
+   * @param int $weight
+   *   The expected weight of the component.
+   *
+   * @internal
+   */
+  protected function assertComponent(string $display_id, string $component_id, string $widget_type, int $weight): void {
+    $component = EntityFormDisplay::load($display_id)->getComponent($component_id);
+    $this->assertIsArray($component);
+    $this->assertSame($widget_type, $component['type']);
+    $this->assertSame($weight, $component['weight']);
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyFieldInstanceTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyFieldInstanceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9f04e00efcaa3333b57b2392398181e72ddc51b0
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyFieldInstanceTest.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d6;
+
+use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\FieldConfigInterface;
+
+/**
+ * Vocabulary field instance migration.
+ *
+ * @group forum
+ */
+class MigrateVocabularyFieldInstanceTest extends MigrateDrupal6TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'comment',
+    'forum',
+    'menu_ui',
+    'taxonomy',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    // Execute Dependency Migrations.
+    $this->migrateContentTypes();
+    $this->installEntitySchema('taxonomy_term');
+    $this->executeMigrations([
+      'd6_node_type',
+      'd6_taxonomy_vocabulary',
+      'd6_vocabulary_field',
+      'd6_vocabulary_field_instance',
+    ]);
+  }
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal6.php';
+  }
+
+  /**
+   * Tests the Drupal 6 vocabulary-node type association migration.
+   */
+  public function testVocabularyFieldInstance() {
+    $this->assertEntity('node.forum.taxonomy_forums', 'Forums', 'entity_reference', FALSE, FALSE);
+    $this->assertEntity('node.forum.field_trees', 'Trees', 'entity_reference', FALSE, FALSE);
+    $this->assertEntity('node.forum.field_freetags', 'FreeTags', 'entity_reference', FALSE, FALSE);
+  }
+
+  /**
+   * Asserts various aspects of a field config entity.
+   *
+   * @param string $id
+   *   The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME.
+   * @param string $expected_label
+   *   The expected field label.
+   * @param string $expected_field_type
+   *   The expected field type.
+   * @param bool $is_required
+   *   Whether or not the field is required.
+   * @param bool $expected_translatable
+   *   Whether or not the field is expected to be translatable.
+   *
+   * @internal
+   */
+  protected function assertEntity(string $id, string $expected_label, string $expected_field_type, bool $is_required, bool $expected_translatable): void {
+    [$expected_entity_type, $expected_bundle, $expected_name] = explode('.', $id);
+
+    /** @var \Drupal\field\FieldConfigInterface $field */
+    $field = FieldConfig::load($id);
+    $this->assertInstanceOf(FieldConfigInterface::class, $field);
+    $this->assertEquals($expected_label, $field->label());
+    $this->assertEquals($expected_field_type, $field->getType());
+    $this->assertEquals($expected_entity_type, $field->getTargetEntityTypeId());
+    $this->assertEquals($expected_bundle, $field->getTargetBundle());
+    $this->assertEquals($expected_name, $field->getName());
+    $this->assertEquals($is_required, $field->isRequired());
+    $this->assertEquals($expected_entity_type . '.' . $expected_name, $field->getFieldStorageDefinition()->id());
+    $this->assertEquals($expected_translatable, $field->isTranslatable());
+  }
+
+  /**
+   * Asserts the settings of a link field config entity.
+   *
+   * @param string $id
+   *   The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME.
+   * @param int $title_setting
+   *   The expected title setting.
+   *
+   * @internal
+   */
+  protected function assertLinkFields(string $id, int $title_setting): void {
+    $field = FieldConfig::load($id);
+    $this->assertSame($title_setting, $field->getSetting('title'));
+  }
+
+  /**
+   * Asserts the settings of an entity reference field config entity.
+   *
+   * @param string $id
+   *   The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME.
+   * @param string[] $target_bundles
+   *   An array of expected target bundles.
+   *
+   * @internal
+   */
+  protected function assertEntityReferenceFields(string $id, array $target_bundles): void {
+    $field = FieldConfig::load($id);
+    $handler_settings = $field->getSetting('handler_settings');
+    $this->assertArrayHasKey('target_bundles', $handler_settings);
+    foreach ($handler_settings['target_bundles'] as $target_bundle) {
+      $this->assertContains($target_bundle, $target_bundles);
+    }
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyFieldTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyFieldTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..100c85c75541a6b856bb0effe6380e12abf0e4f4
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d6/MigrateVocabularyFieldTest.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d6;
+
+use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\field\FieldStorageConfigInterface;
+
+/**
+ * Vocabulary field migration.
+ *
+ * @group forum
+ */
+class MigrateVocabularyFieldTest extends MigrateDrupal6TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'comment',
+    'forum',
+    'taxonomy',
+    'menu_ui',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->migrateTaxonomy();
+  }
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal6.php';
+  }
+
+  /**
+   * Tests the Drupal 6 vocabulary-node type association migration.
+   */
+  public function testVocabularyField() {
+    // Test that the field exists.
+    $this->assertEntity('node.field_freetags', 'entity_reference', TRUE, -1);
+    $this->assertEntity('node.field_trees', 'entity_reference', TRUE, 1);
+    $this->assertEntity('node.taxonomy_forums', 'entity_reference', TRUE, 1);
+  }
+
+  /**
+   * Asserts various aspects of a field_storage_config entity.
+   *
+   * @param string $id
+   *   The entity ID in the form ENTITY_TYPE.FIELD_NAME.
+   * @param string $expected_type
+   *   The expected field type.
+   * @param bool $expected_translatable
+   *   Whether or not the field is expected to be translatable.
+   * @param int $expected_cardinality
+   *   The expected cardinality of the field.
+   *
+   * @internal
+   */
+  protected function assertEntity(string $id, string $expected_type, bool $expected_translatable, int $expected_cardinality): void {
+    [$expected_entity_type, $expected_name] = explode('.', $id);
+
+    /** @var \Drupal\field\FieldStorageConfigInterface $field */
+    $field = FieldStorageConfig::load($id);
+    $this->assertInstanceOf(FieldStorageConfigInterface::class, $field);
+    $this->assertEquals($expected_name, $field->getName());
+    $this->assertEquals($expected_type, $field->getType());
+    $this->assertEquals($expected_translatable, $field->isTranslatable());
+    $this->assertEquals($expected_entity_type, $field->getTargetEntityTypeId());
+
+    if ($expected_cardinality === 1) {
+      $this->assertFalse($field->isMultiple());
+    }
+    else {
+      $this->assertTrue($field->isMultiple());
+    }
+    $this->assertEquals($expected_cardinality, $field->getCardinality());
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f7e8f284a5648b6ea109678942e3413671fdb464
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d7;
+
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Tests\language\Kernel\Migrate\d7\MigrateLanguageContentTaxonomyVocabularySettingsTest as CoreTest;
+
+/**
+ * Tests migration of i18ntaxonomy vocabulary settings.
+ *
+ * @group forum
+ */
+class MigrateLanguageContentTaxonomyVocabularySettingsTest extends CoreTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'comment',
+    'forum',
+  ];
+
+  /**
+   * Tests migration of 18ntaxonomy vocabulary settings.
+   */
+  public function testLanguageContentTaxonomy() {
+    $this->assertLanguageContentSettings('taxonomy_term', 'forums', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..937052e738b23d3a54005df03a28e9064139b4fc
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d7;
+
+use Drupal\Tests\forum\Kernel\Migrate\MigrateTestTrait;
+use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
+use Drupal\taxonomy\Entity\Term;
+
+/**
+ * Test migration of forum taxonomy terms.
+ *
+ * @group forum
+ */
+class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
+
+  use MigrateTestTrait;
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'comment',
+    'forum',
+    'content_translation',
+    'datetime',
+    'datetime_range',
+    'image',
+    'language',
+    'menu_ui',
+    'node',
+    'taxonomy',
+    'text',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->installConfig('forum');
+    $this->installEntitySchema('comment');
+    $this->installEntitySchema('file');
+
+    $this->migrateTaxonomyTerms();
+    $this->executeMigrations([
+      'language',
+      'd7_user_role',
+      'd7_user',
+      'd7_entity_translation_settings',
+      'd7_taxonomy_term_entity_translation',
+    ]);
+  }
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal7.php';
+  }
+
+  /**
+   * Assert the forum taxonomy terms.
+   */
+  public function testTaxonomyTerms() {
+    $this->assertEntity(1, 'en', 'General discussion', 'forums', '', NULL, 2, ['0'], 0);
+
+    $this->assertEntity(5, 'en', 'Custom Forum', 'forums', 'Where the cool kids are.', NULL, 3, ['0'], 0);
+    $this->assertEntity(6, 'en', 'Games', 'forums', NULL, '', 4, ['0'], 1);
+    $this->assertEntity(7, 'en', 'Minecraft', 'forums', '', NULL, 1, [6], 0);
+    $this->assertEntity(8, 'en', 'Half Life 3', 'forums', '', NULL, 0, [6], 0);
+
+    // Verify that we still can create forum containers after the migration.
+    $term = Term::create([
+      'vid' => 'forums',
+      'name' => 'Forum Container',
+      'forum_container' => 1,
+    ]);
+    $term->save();
+
+    // Reset the forums tree data so this new term is included in the tree.
+    unset($this->treeData['forums']);
+    $this->assertEntity(9, 'en', 'Forum Container', 'forums', '', '', 0, ['0'], 1);
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTranslationTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTranslationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..883b47fc0885c1210511302929f42eb2474a262e
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTranslationTest.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d7;
+
+use Drupal\Tests\taxonomy\Kernel\Migrate\d7\MigrateTaxonomyTermTranslationTest as TaxonomyTermTranslationTest;
+
+/**
+ * Test migration of translated taxonomy terms.
+ *
+ * @group forum
+ */
+class MigrateTaxonomyTermTranslationTest extends TaxonomyTermTranslationTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'comment',
+    'forum',
+  ];
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal7.php';
+  }
+
+  /**
+   * Tests the Drupal i18n  taxonomy term to Drupal 8 migration.
+   */
+  public function testTaxonomyTermTranslation() {
+    // Forums vocabulary, no multilingual option.
+    $this->assertEntity(1, 'en', 'General discussion', 'forums', NULL, NULL, '2', []);
+    $this->assertEntity(5, 'en', 'Custom Forum', 'forums', 'Where the cool kids are.', NULL, '3', []);
+    $this->assertEntity(6, 'en', 'Games', 'forums', NULL, NULL, '4', []);
+    $this->assertEntity(7, 'en', 'Minecraft', 'forums', NULL, NULL, '1', ['6']);
+    $this->assertEntity(8, 'en', 'Half Life 3', 'forums', NULL, NULL, '0', ['6']);
+  }
+
+}
diff --git a/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php b/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f1c5b7827f32aab0dd5f9864d7758722ce5d9c90
--- /dev/null
+++ b/core/modules/forum/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\Tests\forum\Kernel\Migrate\d7;
+
+use Drupal\Tests\taxonomy\Kernel\Migrate\d7\MigrateTaxonomyVocabularyTest as TaxonomyVocabularyTest;
+
+/**
+ * Migrate forum vocabulary to taxonomy.vocabulary.*.yml.
+ *
+ * @group forum
+ */
+class MigrateTaxonomyVocabularyTest extends TaxonomyVocabularyTest {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'comment',
+    'forum',
+  ];
+
+  /**
+   * Gets the path to the fixture file.
+   */
+  protected function getFixtureFilePath() {
+    return __DIR__ . '/../../../../fixtures/drupal7.php';
+  }
+
+  /**
+   * Tests the Drupal 7 taxonomy vocabularies to Drupal 8 migration.
+   */
+  public function testTaxonomyVocabulary() {
+    $this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', 0);
+    $this->assertEntity('forums', 'Subject of discussion', 'Forum navigation vocabulary', -10);
+  }
+
+}
diff --git a/core/modules/help/help.module b/core/modules/help/help.module
index 84497f243f1603bd9adb7100d063d86695a5015b..9704b9e3f4d07e6540cb25763d759bbaed87de08 100644
--- a/core/modules/help/help.module
+++ b/core/modules/help/help.module
@@ -36,7 +36,7 @@ function help_help($route_name, RouteMatchInterface $route_match) {
       $search_help = ($module_handler->moduleExists('search')) ? Url::fromRoute('help.page', ['name' => 'search'])->toString() : '#';
       $output = '<h3>' . t('About') . '</h3>';
       $output .= '<p>' . t('The Help module generates <a href=":help-page">Help topics and reference pages</a> to guide you through the use and configuration of modules, and provides a Help block with page-level help. The reference pages are a starting point for <a href=":handbook">Drupal.org online documentation</a> pages that contain more extensive and up-to-date information, are annotated with user-contributed comments, and serve as the definitive reference point for all Drupal documentation. For more information, see the <a href=":help">online documentation for the Help module</a>.', [':help' => 'https://www.drupal.org/documentation/modules/help/', ':handbook' => 'https://www.drupal.org/documentation', ':help-page' => Url::fromRoute('help.main')->toString()]) . '</p>';
-      $output .= '<p>' . t('Help topics provided by modules and themes are also part of the Help module. If the core Search module is enabled, these topics are searchable. For more information, see the <a href=":online">online documentation for Help Topics</a>.', [':online' => 'https://www.drupal.org/documentation/modules/help_topics']) . '</p>';
+      $output .= '<p>' . t('Help topics provided by modules and themes are also part of the Help module. If the core Search module is enabled, these topics are searchable. For more information, see the <a href=":online">online documentation, Help Topic Standards</a>.', [':online' => 'https://www.drupal.org/docs/develop/managing-a-drupalorg-theme-module-or-distribution-project/documenting-your-project/help-topic-standards']) . '</p>';
       $output .= '<h3>' . t('Uses') . '</h3>';
       $output .= '<dl>';
       $output .= '<dt>' . t('Providing a help reference') . '</dt>';
@@ -46,7 +46,7 @@ function help_help($route_name, RouteMatchInterface $route_match) {
       $output .= '<dt>' . t('Viewing help topics') . '</dt>';
       $output .= '<dd>' . t('The top-level help topics are listed on the main <a href=":help_page">Help page</a>. Links to other topics, including non-top-level help topics, can be found under the "Related" heading when viewing a topic page.', [':help_page' => $help_home]) . '</dd>';
       $output .= '<dt>' . t('Providing help topics') . '</dt>';
-      $output .= '<dd>' . t("Modules and themes can provide help topics as Twig-file-based plugins in a project sub-directory called <em>help_topics</em>; plugin meta-data is provided in YAML front matter within each Twig file. Plugin-based help topics provided by modules and themes will automatically be updated when a module or theme is updated. Use the plugins in <em>core/modules/help_topics/help_topics</em> as a guide when writing and formatting a help topic plugin for your theme or module.") . '</dd>';
+      $output .= '<dd>' . t("Modules and themes can provide help topics as Twig-file-based plugins in a project sub-directory called <em>help_topics</em>; plugin meta-data is provided in YAML front matter within each Twig file. Plugin-based help topics provided by modules and themes will automatically be updated when a module or theme is updated. Use the plugins in <em>core/modules/help/help_topics</em> as a guide when writing and formatting a help topic plugin for your theme or module.") . '</dd>';
       $output .= '<dt>' . t('Translating help topics') . '</dt>';
       $output .= '<dd>' . t('The title and body text of help topics provided by contributed modules and themes are translatable using the <a href=":locale_help">Interface Translation module</a>. Topics provided by custom modules and themes are also translatable if they have been viewed at least once in a non-English language, which triggers putting their translatable text into the translation database.', [':locale_help' => $locale_help]) . '</dd>';
       $output .= '<dt>' . t('Configuring help search') . '</dt>';
diff --git a/core/modules/help/tests/modules/help_page_test/help_page_test.module b/core/modules/help/tests/modules/help_page_test/help_page_test.module
index 49aac7a6b75f95a6e02eedf1bf4a4d5efa8f7a41..079bb8a87992d6c986866eb16b3a4c954b210f52 100644
--- a/core/modules/help/tests/modules/help_page_test/help_page_test.module
+++ b/core/modules/help/tests/modules/help_page_test/help_page_test.module
@@ -15,7 +15,7 @@ function help_page_test_help($route_name, RouteMatchInterface $route_match) {
   switch ($route_name) {
     case 'help.page.help_page_test':
       // Make the help text conform to core standards. See
-      // \Drupal\system\Tests\Module\InstallUninstallTest::assertHelp().
+      // \Drupal\system\Tests\Functional\GenericModuleTestBase::assertHookHelp().
       return t('Read the <a href=":url">online documentation for the Help Page Test module</a>.', [':url' => 'http://www.example.com']);
 
     case 'help_page_test.has_help':
diff --git a/core/modules/help/tests/src/Functional/GenericTest.php b/core/modules/help/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0458e2e0c2b3dafd27d4d4ab800e22f23faeca46
--- /dev/null
+++ b/core/modules/help/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\help\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for help.
+ *
+ * @group help
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/help/tests/src/Functional/HelpTopicsSyntaxTest.php b/core/modules/help/tests/src/Functional/HelpTopicsSyntaxTest.php
index b4c3d7f09137dde1184be27df87d6a38b044a516..06f361824096527e593df77bd4b4c210f8fa8270 100644
--- a/core/modules/help/tests/src/Functional/HelpTopicsSyntaxTest.php
+++ b/core/modules/help/tests/src/Functional/HelpTopicsSyntaxTest.php
@@ -13,13 +13,8 @@
 /**
  * Verifies that all core Help topics can be rendered and comply with standards.
  *
- * @todo This test should eventually be folded into
- * Drupal\Tests\system\Functional\Module\InstallUninstallTest
- * when help_topics becomes stable, so that it will test with only one module
- * at a time installed and not duplicate the effort of installing. See issue
- * https://www.drupal.org/project/drupal/issues/3074040
- *
  * @group help
+ * @group #slow
  */
 class HelpTopicsSyntaxTest extends BrowserTestBase {
 
diff --git a/core/modules/help/tests/src/Kernel/HelpSearchPluginTest.php b/core/modules/help/tests/src/Kernel/HelpSearchPluginTest.php
index becce2bf22028a577ce980a3066101bdc1380ab4..a79bd634a3c906fb93964e55e8fccdd08761f0e2 100644
--- a/core/modules/help/tests/src/Kernel/HelpSearchPluginTest.php
+++ b/core/modules/help/tests/src/Kernel/HelpSearchPluginTest.php
@@ -11,7 +11,7 @@
  *
  * @group help
  *
- * @see \Drupal\help_topics\Plugin\Search\HelpSearch
+ * @see \Drupal\help\Plugin\Search\HelpSearch
  */
 class HelpSearchPluginTest extends KernelTestBase {
 
@@ -26,7 +26,7 @@ class HelpSearchPluginTest extends KernelTestBase {
   public function testAnnotation() {
     /** @var \Drupal\search\SearchPluginManager $manager */
     $manager = \Drupal::service('plugin.manager.search');
-    /** @var \Drupal\help_topics\Plugin\Search\HelpSearch $plugin */
+    /** @var \Drupal\help\Plugin\Search\HelpSearch $plugin */
     $plugin = $manager->createInstance('help_search');
     $this->assertInstanceOf(AccessibleInterface::class, $plugin);
     $this->assertInstanceOf(SearchIndexingInterface::class, $plugin);
diff --git a/core/modules/history/tests/src/Functional/GenericTest.php b/core/modules/history/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a242d3113fc394c6df7fa0015cf70103a9c76bf9
--- /dev/null
+++ b/core/modules/history/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\history\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for history.
+ *
+ * @group history
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/image/tests/src/Functional/GenericTest.php b/core/modules/image/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bedf88874e5b10a1aedc6e93fa5cf1fea39d34c6
--- /dev/null
+++ b/core/modules/image/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\image\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for image.
+ *
+ * @group image
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
index 4874e98f49978a44af19794928d2576180e33df0..bb270feda8f12ecdcb0f788c4312479e27591441 100644
--- a/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
+++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
@@ -337,7 +337,7 @@ public function testImageFieldSettings() {
     $this->drupalGet('admin/structure/types/manage/article/fields/node.article.' . $field_name . '/storage');
     $this->submitForm([
       'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
-    ], 'Save field settings');
+    ], 'Save');
     $edit = [
       'files[' . $field_name . '_1][]' => \Drupal::service('file_system')->realpath($test_image->uri),
     ];
@@ -504,7 +504,7 @@ public function testImageFieldDefaultImage() {
       'settings[default_image][title]' => $title,
     ];
     $this->drupalGet("admin/structure/types/manage/article/fields/node.article.{$field_name}/storage");
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     // Clear field definition cache so the new default image is detected.
     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
     $field_storage = FieldStorageConfig::loadByName('node', $field_name);
@@ -560,7 +560,7 @@ public function testImageFieldDefaultImage() {
     // Can't use fillField cause Mink can't fill hidden fields.
     $this->drupalGet("admin/structure/types/manage/article/fields/node.article.$field_name/storage");
     $this->getSession()->getPage()->find('css', 'input[name="settings[default_image][uuid][fids]"]')->setValue(0);
-    $this->getSession()->getPage()->pressButton('Save field settings');
+    $this->getSession()->getPage()->pressButton('Save');
 
     // Clear field definition cache so the new default image is detected.
     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
@@ -579,7 +579,7 @@ public function testImageFieldDefaultImage() {
       'settings[default_image][title]' => $title,
     ];
     $this->drupalGet('admin/structure/types/manage/article/fields/node.article.' . $private_field_name . '/storage');
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     // Clear field definition cache so the new default image is detected.
     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
 
diff --git a/core/modules/inline_form_errors/tests/src/Functional/GenericTest.php b/core/modules/inline_form_errors/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d6f294ec86b3fe5c68fb62a30855951ae0b5150
--- /dev/null
+++ b/core/modules/inline_form_errors/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\inline_form_errors\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for inline_form_errors.
+ *
+ * @group inline_form_errors
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/jsonapi/jsonapi.services.yml b/core/modules/jsonapi/jsonapi.services.yml
index c989066498a786055cc6d7aad88bc8c1a8262548..e6900aa62885732429d3128153ec5935249a641b 100644
--- a/core/modules/jsonapi/jsonapi.services.yml
+++ b/core/modules/jsonapi/jsonapi.services.yml
@@ -50,7 +50,8 @@ services:
   jsonapi.normalization_cacher:
     class: Drupal\jsonapi\EventSubscriber\ResourceObjectNormalizationCacher
     calls:
-      - ['setRenderCache', ['@render_cache']]
+      - ['setVariationCache', ['@variation_cache.jsonapi_normalizations']]
+      - ['setRequestStack', ['@request_stack']]
     tags:
       - { name: event_subscriber }
   serializer.normalizer.content_entity.jsonapi:
@@ -144,6 +145,10 @@ services:
       - { name: cache.bin }
     factory: ['@cache_factory', 'get']
     arguments: [jsonapi_normalizations]
+  variation_cache.jsonapi_normalizations:
+    class: Drupal\Core\Cache\VariationCacheInterface
+    factory: ['@variation_cache_factory', 'get']
+    arguments: [jsonapi_normalizations]
 
   # Route filter.
   jsonapi.route_filter.format_setter:
diff --git a/core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php b/core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php
index 1e801281487cf9f50959cab5b14b00f6e0b76629..3a5b818dc5bb1dc4255e7566e23bb96d0b516788 100644
--- a/core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php
+++ b/core/modules/jsonapi/src/EventSubscriber/ResourceObjectNormalizationCacher.php
@@ -3,9 +3,10 @@
 namespace Drupal\jsonapi\EventSubscriber;
 
 use Drupal\Core\Cache\CacheableMetadata;
-use Drupal\Core\Render\RenderCacheInterface;
+use Drupal\Core\Cache\VariationCacheInterface;
 use Drupal\jsonapi\JsonApiResource\ResourceObject;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\HttpKernel\Event\TerminateEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
 
@@ -14,7 +15,6 @@
  *
  * @internal
  * @see \Drupal\jsonapi\Normalizer\ResourceObjectNormalizer::getNormalization()
- * @todo Refactor once https://www.drupal.org/node/2551419 lands.
  */
 class ResourceObjectNormalizationCacher implements EventSubscriberInterface {
 
@@ -40,11 +40,18 @@ class ResourceObjectNormalizationCacher implements EventSubscriberInterface {
   const RESOURCE_CACHE_SUBSET_FIELDS = 'fields';
 
   /**
-   * The render cache.
+   * The variation cache.
    *
-   * @var \Drupal\Core\Render\RenderCacheInterface
+   * @var \Drupal\Core\Cache\VariationCacheInterface
    */
-  protected $renderCache;
+  protected $variationCache;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
 
   /**
    * The things to cache after the response has been sent.
@@ -54,13 +61,23 @@ class ResourceObjectNormalizationCacher implements EventSubscriberInterface {
   protected $toCache = [];
 
   /**
-   * Sets the render cache service.
+   * Sets the variation cache.
+   *
+   * @param \Drupal\Core\Cache\VariationCacheInterface $variation_cache
+   *   The variation cache.
+   */
+  public function setVariationCache(VariationCacheInterface $variation_cache) {
+    $this->variationCache = $variation_cache;
+  }
+
+  /**
+   * Sets the request stack.
    *
-   * @param \Drupal\Core\Render\RenderCacheInterface $render_cache
-   *   The render cache.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
    */
-  public function setRenderCache(RenderCacheInterface $render_cache) {
-    $this->renderCache = $render_cache;
+  public function setRequestStack(RequestStack $request_stack) {
+    $this->requestStack = $request_stack;
   }
 
   /**
@@ -78,8 +95,14 @@ public function setRenderCache(RenderCacheInterface $render_cache) {
    * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::renderArrayToResponse()
    */
   public function get(ResourceObject $object) {
-    $cached = $this->renderCache->get(static::generateLookupRenderArray($object));
-    return $cached ? $cached['#data'] : FALSE;
+    // @todo Investigate whether to cache POST and PATCH requests.
+    // @todo Follow up on https://www.drupal.org/project/drupal/issues/3381898.
+    if (!$this->requestStack->getCurrentRequest()->isMethodCacheable()) {
+      return FALSE;
+    }
+
+    $cached = $this->variationCache->get($this->generateCacheKeys($object), new CacheableMetadata());
+    return $cached ? $cached->data : FALSE;
   }
 
   /**
@@ -122,52 +145,36 @@ public function onTerminate(TerminateEvent $event) {
    *   The resource object for which to generate a cache item.
    * @param array $normalization_parts
    *   The normalization parts to cache.
-   *
-   * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::responseToRenderArray()
-   * @todo Refactor/remove once https://www.drupal.org/node/2551419 lands.
    */
   protected function set(ResourceObject $object, array $normalization_parts) {
-    $base = static::generateLookupRenderArray($object);
-    $data_as_render_array = $base + [
-      // The data we actually care about.
-      '#data' => $normalization_parts,
-      // Tell RenderCache to cache the #data property: the data we actually care
-      // about.
-      '#cache_properties' => ['#data'],
-      // These exist only to fulfill the requirements of the RenderCache, which
-      // is designed to work with render arrays only. We don't care about these.
-      '#markup' => '',
-      '#attached' => '',
-    ];
+    // @todo Investigate whether to cache POST and PATCH requests.
+    // @todo Follow up on https://www.drupal.org/project/drupal/issues/3381898.
+    if (!$this->requestStack->getCurrentRequest()->isMethodCacheable()) {
+      return;
+    }
 
     // Merge the entity's cacheability metadata with that of the normalization
-    // parts, so that RenderCache can take care of cache redirects for us.
-    CacheableMetadata::createFromObject($object)
+    // parts, so that VariationCache can take care of cache redirects for us.
+    $cacheability = CacheableMetadata::createFromObject($object)
       ->merge(static::mergeCacheableDependencies($normalization_parts[static::RESOURCE_CACHE_SUBSET_BASE]))
-      ->merge(static::mergeCacheableDependencies($normalization_parts[static::RESOURCE_CACHE_SUBSET_FIELDS]))
-      ->applyTo($data_as_render_array);
+      ->merge(static::mergeCacheableDependencies($normalization_parts[static::RESOURCE_CACHE_SUBSET_FIELDS]));
 
-    $this->renderCache->set($data_as_render_array, $base);
+    $this->variationCache->set($this->generateCacheKeys($object), $normalization_parts, $cacheability, new CacheableMetadata());
   }
 
   /**
-   * Generates a lookup render array for a normalization.
+   * Generates the cache keys for a normalization.
    *
    * @param \Drupal\jsonapi\JsonApiResource\ResourceObject $object
-   *   The resource object for which to generate a cache item.
+   *   The resource object for which to generate the cache keys.
    *
-   * @return array
-   *   A render array for use with the RenderCache service.
+   * @return string[]
+   *   The cache keys to pass to the variation cache.
    *
    * @see \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber::$dynamicPageCacheRedirectRenderArray
    */
-  protected static function generateLookupRenderArray(ResourceObject $object) {
-    return [
-      '#cache' => [
-        'keys' => [$object->getResourceType()->getTypeName(), $object->getId(), $object->getLanguage()->getId()],
-        'bin' => 'jsonapi_normalizations',
-      ],
-    ];
+  protected static function generateCacheKeys(ResourceObject $object) {
+    return [$object->getResourceType()->getTypeName(), $object->getId(), $object->getLanguage()->getId()];
   }
 
   /**
diff --git a/core/modules/jsonapi/src/JsonApiResource/Link.php b/core/modules/jsonapi/src/JsonApiResource/Link.php
index 5c1953c1f52c5e425735dea9244e2b810fe4b827..c16f916ecdb1e9d9a07a3853ceb15ae570c51bb7 100644
--- a/core/modules/jsonapi/src/JsonApiResource/Link.php
+++ b/core/modules/jsonapi/src/JsonApiResource/Link.php
@@ -39,12 +39,9 @@ final class Link implements CacheableDependencyInterface {
   protected $href;
 
   /**
-   * The link relation types.
+   * The link relation type.
    *
-   * @var string[]
-   *
-   * @todo: change this type documentation to be a single string in
-   *   https://www.drupal.org/project/drupal/issues/3080467.
+   * @var string
    */
   protected $rel;
 
diff --git a/core/modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php b/core/modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php
index 73700aa0fd2aaa2bd9675bc15559d21f17669a98..6ca47ae23340fa3b6b2bc49bb10d5d7653e17c48 100644
--- a/core/modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php
+++ b/core/modules/jsonapi/src/Normalizer/HttpExceptionNormalizer.php
@@ -44,6 +44,12 @@ public function __construct(AccountInterface $current_user) {
   public function normalize($object, $format = NULL, array $context = []): array|string|int|float|bool|\ArrayObject|NULL {
     $cacheability = new CacheableMetadata();
     $cacheability->addCacheableDependency($object);
+
+    $cacheability->addCacheTags(['config:system.logging']);
+    if (\Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE) {
+      $cacheability->setCacheMaxAge(0);
+    }
+
     return new HttpExceptionNormalizerValue($cacheability, static::rasterizeValueRecursive($this->buildErrorObjects($object)));
   }
 
@@ -82,7 +88,10 @@ protected function buildErrorObjects(HttpException $exception) {
     if ($exception->getCode() !== 0) {
       $error['code'] = (string) $exception->getCode();
     }
-    if ($this->currentUser->hasPermission('access site reports')) {
+
+    $is_verbose_reporting = \Drupal::config('system.logging')->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE;
+    $site_report_access = $this->currentUser->hasPermission('access site reports');
+    if ($site_report_access && $is_verbose_reporting) {
       // The following information may contain sensitive information. Only show
       // it to authorized users.
       $error['source'] = [
diff --git a/core/modules/jsonapi/src/Revisions/NegotiatorBase.php b/core/modules/jsonapi/src/Revisions/NegotiatorBase.php
index 2253090353aeca2aba463c7391e96825b8b881b6..35716f50a79d6bfb2f24e1ffe952eef85a6ce85f 100644
--- a/core/modules/jsonapi/src/Revisions/NegotiatorBase.php
+++ b/core/modules/jsonapi/src/Revisions/NegotiatorBase.php
@@ -75,6 +75,7 @@ public function getRevision(EntityInterface $entity, $version_argument) {
    *   Thrown if the storage handler couldn't be loaded.
    */
   protected function loadRevision(EntityInterface $entity, $revision_id) {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
     $revision = static::ensureVersionExists($storage->loadRevision($revision_id));
     if ($revision->id() !== $entity->id()) {
diff --git a/core/modules/jsonapi/tests/src/Functional/ActionTest.php b/core/modules/jsonapi/tests/src/Functional/ActionTest.php
index ba0b527c4c1779f37ae594f2f160632228507f14..659457121e480e389a013d7ac72a6d08d536334e 100644
--- a/core/modules/jsonapi/tests/src/Functional/ActionTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ActionTest.php
@@ -10,6 +10,7 @@
  * JSON:API integration test for the "Action" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ActionTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/BaseFieldOverrideTest.php b/core/modules/jsonapi/tests/src/Functional/BaseFieldOverrideTest.php
index 6bd47a595e8290657984eabeae27283b1e8f5894..9bd4a18994d8a5dc3b551f154c76b89a83962010 100644
--- a/core/modules/jsonapi/tests/src/Functional/BaseFieldOverrideTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/BaseFieldOverrideTest.php
@@ -10,6 +10,7 @@
  * JSON:API integration test for the "BaseFieldOverride" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class BaseFieldOverrideTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/BlockContentTest.php b/core/modules/jsonapi/tests/src/Functional/BlockContentTest.php
index 2df9ae0b2af4fff5943e7f27a647e5fdbabce2b6..a0d689169c12e3d880429c97d4370209b8bffede 100644
--- a/core/modules/jsonapi/tests/src/Functional/BlockContentTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/BlockContentTest.php
@@ -12,6 +12,7 @@
  * JSON:API integration test for the "BlockContent" content entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class BlockContentTest extends ResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/BlockContentTypeTest.php b/core/modules/jsonapi/tests/src/Functional/BlockContentTypeTest.php
index 7a61e56fd238ace6a5b19cf0303994f7d4653c15..1ecdc78a371cf585a039beba21b9f63e31febcd2 100644
--- a/core/modules/jsonapi/tests/src/Functional/BlockContentTypeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/BlockContentTypeTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "BlockContentType" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class BlockContentTypeTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/BlockTest.php b/core/modules/jsonapi/tests/src/Functional/BlockTest.php
index 41911a900c3d831676d93660693f71c6ccd9f5e1..0341af865cb7430785232222d013c4ddd082b205 100644
--- a/core/modules/jsonapi/tests/src/Functional/BlockTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/BlockTest.php
@@ -10,6 +10,7 @@
  * JSON:API integration test for the "Block" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class BlockTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/CommentTest.php b/core/modules/jsonapi/tests/src/Functional/CommentTest.php
index ffc4db25356c0899ced2d6dbf77d4aca0d17072c..a291e53982574ebdeff6d0e0867c104cade7f74f 100644
--- a/core/modules/jsonapi/tests/src/Functional/CommentTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/CommentTest.php
@@ -21,6 +21,7 @@
  * JSON:API integration test for the "Comment" content entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class CommentTest extends ResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/CommentTypeTest.php b/core/modules/jsonapi/tests/src/Functional/CommentTypeTest.php
index e3654b49e9d2906f94f0273e900134a01c57e2b6..08638cf7d442c74b30dc212d311c00409fd5532f 100644
--- a/core/modules/jsonapi/tests/src/Functional/CommentTypeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/CommentTypeTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "CommentType" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class CommentTypeTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/ConfigTestTest.php b/core/modules/jsonapi/tests/src/Functional/ConfigTestTest.php
index df19a11b85f7759dde9cc8258e68789074b389d9..abd6b548363d6e9875b26b400fd7964856def378 100644
--- a/core/modules/jsonapi/tests/src/Functional/ConfigTestTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ConfigTestTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "ConfigTest" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ConfigTestTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/ConfigurableLanguageTest.php b/core/modules/jsonapi/tests/src/Functional/ConfigurableLanguageTest.php
index ad2c26177992cb7e0b31ab7bf6cc8aa94f501a1b..777235cee4746a89671bb7e68b2e4dfbcdef2a1c 100644
--- a/core/modules/jsonapi/tests/src/Functional/ConfigurableLanguageTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ConfigurableLanguageTest.php
@@ -13,6 +13,7 @@
  * JSON:API integration test for the "ConfigurableLanguage" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ConfigurableLanguageTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/ContactFormTest.php b/core/modules/jsonapi/tests/src/Functional/ContactFormTest.php
index b91d4b7d5acb6c4378eae76a14ebe99cd74e42c2..4887025075d2af2f2608b73fc950986b31cb879e 100644
--- a/core/modules/jsonapi/tests/src/Functional/ContactFormTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ContactFormTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "ContactForm" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ContactFormTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/ContentLanguageSettingsTest.php b/core/modules/jsonapi/tests/src/Functional/ContentLanguageSettingsTest.php
index 608254907627c4d8120dd8944fbea07dc78a0f99..44ca329196131c46c0bc2b1bc23198b4737ad3aa 100644
--- a/core/modules/jsonapi/tests/src/Functional/ContentLanguageSettingsTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ContentLanguageSettingsTest.php
@@ -12,6 +12,7 @@
  * JSON:API integration test for "ContentLanguageSettings" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ContentLanguageSettingsTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/DateFormatTest.php b/core/modules/jsonapi/tests/src/Functional/DateFormatTest.php
index 42e673c526b7f4a87abcbfb6c562201561e4bf34..e6ba774650a1457a0e2ac248e045886ebdf036be 100644
--- a/core/modules/jsonapi/tests/src/Functional/DateFormatTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/DateFormatTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "DateFormat" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class DateFormatTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/EditorTest.php b/core/modules/jsonapi/tests/src/Functional/EditorTest.php
index 8dc45e7e784ba37574ad0e07b0f390dbcbba3a24..c381709192c539e3fcd53319cf51d355185bb681 100644
--- a/core/modules/jsonapi/tests/src/Functional/EditorTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/EditorTest.php
@@ -13,6 +13,7 @@
  * JSON:API integration test for the "Editor" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class EditorTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/EntityFormDisplayTest.php b/core/modules/jsonapi/tests/src/Functional/EntityFormDisplayTest.php
index b4162427f706eb5e4aa00749fd241a0b3ec97bcd..afaf28eaf94b3e7c7caf42670b898f128d6d6a93 100644
--- a/core/modules/jsonapi/tests/src/Functional/EntityFormDisplayTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/EntityFormDisplayTest.php
@@ -10,6 +10,7 @@
  * JSON:API integration test for the "EntityFormDisplay" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class EntityFormDisplayTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/EntityFormModeTest.php b/core/modules/jsonapi/tests/src/Functional/EntityFormModeTest.php
index fb8ac77bbd182609a0cd20665aee8da26952e492..9ab5e05fe85426ca7195feb81951a1193c90171a 100644
--- a/core/modules/jsonapi/tests/src/Functional/EntityFormModeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/EntityFormModeTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "EntityFormMode" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class EntityFormModeTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/EntityViewModeTest.php b/core/modules/jsonapi/tests/src/Functional/EntityViewModeTest.php
index 8a8cc816ae548d382f620218d24f5359835123fa..10def2c5c3e2e5c54ab6f738806b9e0b39a8b720 100644
--- a/core/modules/jsonapi/tests/src/Functional/EntityViewModeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/EntityViewModeTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "EntityViewMode" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class EntityViewModeTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php b/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php
index d5ed11bededdbb7b84d25862391ce072f6f5cb5b..c5d94575104d16c29ecd76768545ec83f53c2c7b 100644
--- a/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/FieldConfigTest.php
@@ -13,6 +13,7 @@
  * JSON:API integration test for the "FieldConfig" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class FieldConfigTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/FieldStorageConfigTest.php b/core/modules/jsonapi/tests/src/Functional/FieldStorageConfigTest.php
index d7f770803aecfb3627b5afc5a19cd3613dc7c326..8e5659351990d71b8d2b9269d008c9859f6b3eb8 100644
--- a/core/modules/jsonapi/tests/src/Functional/FieldStorageConfigTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/FieldStorageConfigTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "FieldStorageConfig" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class FieldStorageConfigTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/FileTest.php b/core/modules/jsonapi/tests/src/Functional/FileTest.php
index bdcb9d4fa7b8b2be2b8d49a44e68b70f49d66183..5b7ff9154dd5836fcbaf64f80b4b26c2a76c3a5f 100644
--- a/core/modules/jsonapi/tests/src/Functional/FileTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/FileTest.php
@@ -15,6 +15,7 @@
  * JSON:API integration test for the "File" content entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class FileTest extends ResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php b/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php
index b054b694b23fbcc066d4086ad4db089740630eeb..7ccd20b2ac40b893d1ab8a13f39a492bd0032fea 100644
--- a/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/FileUploadTest.php
@@ -20,6 +20,7 @@
  * Tests binary data file upload route.
  *
  * @group jsonapi
+ * @group #slow
  */
 class FileUploadTest extends ResourceTestBase {
 
@@ -382,10 +383,7 @@ protected function getPostDocument() {
   /**
    * Tests using the file upload POST route with invalid headers.
    */
-  public function testPostFileUploadInvalidHeaders() {
-    $this->setUpAuthorization('POST');
-    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
-
+  protected function testPostFileUploadInvalidHeaders() {
     $uri = Url::fromUri('base:' . static::$postUri);
 
     // The wrong content type header should return a 415 code.
@@ -444,24 +442,6 @@ public function testPostFileUploadDuplicateFile() {
 
     // Check the actual file data.
     $this->assertSame($this->testFileData, file_get_contents('public://foobar/example_0.txt'));
-  }
-
-  /**
-   * Tests using the file upload POST route twice, simulating a race condition.
-   *
-   * A validation error should occur when the filenames are not unique.
-   */
-  public function testPostFileUploadDuplicateFileRaceCondition() {
-    $this->setUpAuthorization('POST');
-    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
-
-    $uri = Url::fromUri('base:' . static::$postUri);
-
-    // This request will have the default 'application/octet-stream' content
-    // type header.
-    $response = $this->fileRequest($uri, $this->testFileData);
-
-    $this->assertSame(201, $response->getStatusCode());
 
     // Simulate a race condition where two files are uploaded at almost the same
     // time, by removing the first uploaded file from disk (leaving the entry in
@@ -522,6 +502,17 @@ public function testFileUploadStrippedFilePath() {
     $this->assertSame($this->testFileData, file_get_contents('public://foobar/passwd'));
   }
 
+  /**
+   * Tests invalid file uploads.
+   */
+  public function testInvalidFileUploads() {
+    $this->setUpAuthorization('POST');
+    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
+    $this->testPostFileUploadInvalidHeaders();
+    $this->testFileUploadLargerFileSize();
+    $this->testFileUploadMaliciousExtension();
+  }
+
   /**
    * Tests using the file upload route with a unicode file name.
    */
@@ -582,15 +573,12 @@ public function testFileUploadInvalidFileType() {
   /**
    * Tests using the file upload route with a file size larger than allowed.
    */
-  public function testFileUploadLargerFileSize() {
+  protected function testFileUploadLargerFileSize() {
     // Set a limit of 50 bytes.
     $this->field->setSetting('max_filesize', 50)
       ->save();
     $this->rebuildAll();
 
-    $this->setUpAuthorization('POST');
-    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
-
     $uri = Url::fromUri('base:' . static::$postUri);
 
     // Generate a string larger than the 50 byte limit set.
@@ -605,14 +593,11 @@ public function testFileUploadLargerFileSize() {
   /**
    * Tests using the file upload POST route with malicious extensions.
    */
-  public function testFileUploadMaliciousExtension() {
+  protected function testFileUploadMaliciousExtension() {
     // Allow all file uploads but system.file::allow_insecure_uploads is set to
     // FALSE.
     $this->field->setSetting('file_extensions', '')->save();
 
-    $this->setUpAuthorization('POST');
-    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
-
     $uri = Url::fromUri('base:' . static::$postUri);
 
     $php_string = '<?php print "Drupal"; ?>';
@@ -722,9 +707,9 @@ public function testFileUploadMaliciousExtension() {
   }
 
   /**
-   * Tests using the file upload POST route no extension configured.
+   * Tests using the file upload POST route no configuration.
    */
-  public function testFileUploadNoExtensionSetting() {
+  public function testFileUploadNoConfiguration() {
     $this->setUpAuthorization('POST');
     $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
 
@@ -739,22 +724,11 @@ public function testFileUploadNoExtensionSetting() {
 
     $this->assertResponseData($expected, $response);
     $this->assertFileExists('public://foobar/example.txt');
-  }
-
-  /**
-   * Tests using the file upload POST route no directory configured.
-   */
-  public function testFileUploadNoDirectorySetting() {
-    $this->setUpAuthorization('POST');
-    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);
-
-    $uri = Url::fromUri('base:' . static::$postUri);
-
     $this->field->setSetting('file_directory', '')
       ->save();
 
     $response = $this->fileRequest($uri, $this->testFileData, ['Content-Disposition' => 'filename="example.txt"']);
-    $expected = $this->getExpectedDocument(1, 'example.txt', TRUE);
+    $expected = $this->getExpectedDocument(2, 'example.txt', TRUE);
     $expected['data']['attributes']['uri']['value'] = 'public://example.txt';
     $expected['data']['attributes']['uri']['url'] = base_path() . $this->siteDirectory . '/files/example.txt';
 
diff --git a/core/modules/jsonapi/tests/src/Functional/FilterFormatTest.php b/core/modules/jsonapi/tests/src/Functional/FilterFormatTest.php
index c40e249d6880e4b30b741e6918646f4be1f05aef..1fe8b3e116fad97e9396216f5ef9c5f8dab7730f 100644
--- a/core/modules/jsonapi/tests/src/Functional/FilterFormatTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/FilterFormatTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "FilterFormat" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class FilterFormatTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/GenericTest.php b/core/modules/jsonapi/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..583ebb7b56022ce17aaf8dd7429b2df28e2d78d2
--- /dev/null
+++ b/core/modules/jsonapi/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for jsonapi.
+ *
+ * @group jsonapi
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/jsonapi/tests/src/Functional/ImageStyleTest.php b/core/modules/jsonapi/tests/src/Functional/ImageStyleTest.php
index 1bafcf55bd76f114afbb799b79244608e31ff1b9..10aeabac0aad138bb2fbbd317feb685983dfc6bc 100644
--- a/core/modules/jsonapi/tests/src/Functional/ImageStyleTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ImageStyleTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "ImageStyle" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ImageStyleTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php b/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php
index dcd442f553615c8939a8b0b6fc32dd4bc85817dc..c084bc6e31616d744f6f8f1143faaa3be200725b 100644
--- a/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/JsonApiRegressionTest.php
@@ -31,6 +31,7 @@
  * JSON:API regression tests.
  *
  * @group jsonapi
+ * @group #slow
  *
  * @internal
  */
diff --git a/core/modules/jsonapi/tests/src/Functional/MediaTest.php b/core/modules/jsonapi/tests/src/Functional/MediaTest.php
index 8a4ac33ef0afdde239e4ceaca723d8e9887cf372..d169934048f1f3e3790e12a95ac863aeb51acbaf 100644
--- a/core/modules/jsonapi/tests/src/Functional/MediaTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/MediaTest.php
@@ -14,6 +14,7 @@
  * JSON:API integration test for the "Media" content entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class MediaTest extends ResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/MediaTypeTest.php b/core/modules/jsonapi/tests/src/Functional/MediaTypeTest.php
index 1e795eb3f19e13bf6c73c4494fad18cbdd6adad8..f8dbc1528b53e42eacb7ea94179c32d91987cd3b 100644
--- a/core/modules/jsonapi/tests/src/Functional/MediaTypeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/MediaTypeTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "MediaType" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class MediaTypeTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php b/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php
index b1e7fe299a99f404572337be26fe4108df4cbbb1..617d454e0ea5430249744b470444fd2aee56df72 100644
--- a/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/MenuLinkContentTest.php
@@ -13,6 +13,7 @@
  * JSON:API integration test for the "MenuLinkContent" content entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class MenuLinkContentTest extends ResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/MenuTest.php b/core/modules/jsonapi/tests/src/Functional/MenuTest.php
index c367ab61795931e4c80c034c44eccb6b7f91b1b9..e2e82c88da7364c03fd5086df566b78343577462 100644
--- a/core/modules/jsonapi/tests/src/Functional/MenuTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/MenuTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "Menu" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class MenuTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/MessageTest.php b/core/modules/jsonapi/tests/src/Functional/MessageTest.php
index 045848862f3c1dc3525ed6e7ba92e5a7305d761c..e8e6f03527bb4f63c8089f611409093019ad44c2 100644
--- a/core/modules/jsonapi/tests/src/Functional/MessageTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/MessageTest.php
@@ -13,6 +13,7 @@
  * JSON:API integration test for the "Message" content entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class MessageTest extends ResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTest.php
index 4fb79a7c66e3f611e8afa288f5febba7a221f211..376d4a7bc29bbf1a944adfc1468b7cc2e7cb2e57 100644
--- a/core/modules/jsonapi/tests/src/Functional/NodeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/NodeTest.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Url;
 use Drupal\jsonapi\Normalizer\HttpExceptionNormalizer;
 use Drupal\jsonapi\Normalizer\Value\CacheableNormalization;
@@ -19,6 +20,7 @@
  * JSON:API integration test for the "Node" content entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class NodeTest extends ResourceTestBase {
 
@@ -386,12 +388,7 @@ protected function assertCacheableNormalizations(): void {
     $this->entity->save();
     $uuid = $this->entity->uuid();
     $language = $this->entity->language()->getId();
-    $cache = \Drupal::service('render_cache')->get([
-      '#cache' => [
-        'keys' => ['node--camelids', $uuid, $language],
-        'bin' => 'jsonapi_normalizations',
-      ],
-    ]);
+    $cache = \Drupal::service('variation_cache.jsonapi_normalizations')->get(['node--camelids', $uuid, $language], new CacheableMetadata());
     // After saving the entity the normalization should not be cached.
     $this->assertFalse($cache);
     // @todo Remove line below in favor of commented line in https://www.drupal.org/project/drupal/issues/2878463.
@@ -422,13 +419,8 @@ protected function assertCacheableNormalizations(): void {
    * @internal
    */
   protected function assertNormalizedFieldsAreCached(array $field_names): void {
-    $cache = \Drupal::service('render_cache')->get([
-      '#cache' => [
-        'keys' => ['node--camelids', $this->entity->uuid(), $this->entity->language()->getId()],
-        'bin' => 'jsonapi_normalizations',
-      ],
-    ]);
-    $cached_fields = $cache['#data']['fields'];
+    $cache = \Drupal::service('variation_cache.jsonapi_normalizations')->get(['node--camelids', $this->entity->uuid(), $this->entity->language()->getId()], new CacheableMetadata());
+    $cached_fields = $cache->data['fields'];
     $this->assertSameSize($field_names, $cached_fields);
     array_walk($field_names, function ($field_name) use ($cached_fields) {
       $this->assertInstanceOf(
diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php
index ea93316bbd12620f95455026760e05a7be2673da..525a29bf55fdf02efb489dcf7fcab40c052ab931 100644
--- a/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "NodeType" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class NodeTypeTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php b/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php
index 228c242c95a37159fdf9b3e0cf0b21f2cd8cae20..2ad07816d222124afb2a4bbdeab93cfc136307a6 100644
--- a/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php
@@ -10,6 +10,7 @@
  *
  * @group jsonapi
  * @group path
+ * @group #slow
  */
 class PathAliasTest extends ResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php b/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
index 2818fe4f4e551279a9ff6cbabb533911ce8d6fc2..7146b846026fe3ca1a5ea9a6ee2a1288f17dee8d 100644
--- a/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
+++ b/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
@@ -222,6 +222,8 @@ protected function setUp(): void {
 
     $this->serializer = $this->container->get('jsonapi.serializer');
 
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
+
     // Ensure the anonymous user role has no permissions at all.
     $user_role = Role::load(RoleInterface::ANONYMOUS_ID);
     foreach ($user_role->getPermissions() as $permission) {
@@ -726,7 +728,14 @@ protected function assertResourceResponse($expected_status_code, $expected_docum
     // Expected cache tags: X-Drupal-Cache-Tags header.
     $this->assertSame($expected_cache_tags !== FALSE, $response->hasHeader('X-Drupal-Cache-Tags'));
     if (is_array($expected_cache_tags)) {
-      $this->assertEqualsCanonicalizing($expected_cache_tags, explode(' ', $response->getHeader('X-Drupal-Cache-Tags')[0]));
+      $actual_cache_tags = explode(' ', $response->getHeader('X-Drupal-Cache-Tags')[0]);
+
+      $tag = 'config:system.logging';
+      if (!in_array($tag, $expected_cache_tags) && in_array($tag, $actual_cache_tags)) {
+        $expected_cache_tags[] = $tag;
+      }
+
+      $this->assertEqualsCanonicalizing($expected_cache_tags, $actual_cache_tags);
     }
 
     // Expected cache contexts: X-Drupal-Cache-Contexts header.
@@ -3129,9 +3138,14 @@ public function testRevisions() {
       [$revision_id, $relationship_url, $related_url] = $revision_case;
       // Load the revision that will be requested.
       $this->entityStorage->resetCache([$entity->id()]);
-      $revision = is_null($revision_id)
-        ? $this->entityStorage->load($entity->id())
-        : $this->entityStorage->loadRevision($revision_id);
+      if ($revision_id === NULL) {
+        $revision = $this->entityStorage->load($entity->id());
+      }
+      else {
+        /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+        $storage = $this->entityStorage;
+        $revision = $storage->loadRevision($revision_id);
+      }
       // Request the relationship resource without access to the relationship
       // field.
       $actual_response = $this->request('GET', $relationship_url, $request_options);
@@ -3156,9 +3170,14 @@ public function testRevisions() {
       [$revision_id, $relationship_url, $related_url] = $revision_case;
       // Load the revision that will be requested.
       $this->entityStorage->resetCache([$entity->id()]);
-      $revision = is_null($revision_id)
-        ? $this->entityStorage->load($entity->id())
-        : $this->entityStorage->loadRevision($revision_id);
+      if ($revision_id === NULL) {
+        $revision = $this->entityStorage->load($entity->id());
+      }
+      else {
+        /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+        $storage = $this->entityStorage;
+        $revision = $storage->loadRevision($revision_id);
+      }
       // Request the relationship resource after granting access to the
       // relationship field.
       $actual_response = $this->request('GET', $relationship_url, $request_options);
diff --git a/core/modules/jsonapi/tests/src/Functional/ResponsiveImageStyleTest.php b/core/modules/jsonapi/tests/src/Functional/ResponsiveImageStyleTest.php
index 32b14bd961127b7495ed915b6ef00588ccd3e418..a54e7dbde6f9e7bd9220abbb11764597ff581783 100644
--- a/core/modules/jsonapi/tests/src/Functional/ResponsiveImageStyleTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ResponsiveImageStyleTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "ResponsiveImageStyle" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ResponsiveImageStyleTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php b/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php
index ec379ab881addd5d8935de9d5eb65d60d344ce1b..84b8b91fb8e49938f753bfac50916a98da2b4b96 100644
--- a/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php
+++ b/core/modules/jsonapi/tests/src/Functional/RestJsonApiUnsupported.php
@@ -65,6 +65,8 @@ protected function setUpAuthorization($method) {
   protected function setUp(): void {
     parent::setUp();
 
+    $this->config('system.logging')->set('error_level', ERROR_REPORTING_HIDE)->save();
+
     // Create a "Camelids" node type.
     NodeType::create([
       'name' => 'Camelids',
@@ -99,7 +101,7 @@ public function testApiJsonNotSupportedInRest() {
       400,
       FALSE,
       $response,
-      ['4xx-response', 'config:user.role.anonymous', 'http_response', 'node:1'],
+      ['4xx-response', 'config:system.logging', 'config:user.role.anonymous', 'http_response', 'node:1'],
       ['url.query_args:_format', 'url.site', 'user.permissions'],
       'MISS',
       'MISS'
diff --git a/core/modules/jsonapi/tests/src/Functional/RestResourceConfigTest.php b/core/modules/jsonapi/tests/src/Functional/RestResourceConfigTest.php
index 5dbeeff934ef5164c529318161c5cb88756e34cd..8e0d03e9271bf8a274b8f39a7128b9a1b0d02a4c 100644
--- a/core/modules/jsonapi/tests/src/Functional/RestResourceConfigTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/RestResourceConfigTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "RestResourceConfig" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class RestResourceConfigTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/RoleTest.php b/core/modules/jsonapi/tests/src/Functional/RoleTest.php
index a5116132252bd508451105e9f1e11aacbfd35a5d..0880da627194bad0f66bd795ede2ff8c1ccefbd5 100644
--- a/core/modules/jsonapi/tests/src/Functional/RoleTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/RoleTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "Role" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class RoleTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/SearchPageTest.php b/core/modules/jsonapi/tests/src/Functional/SearchPageTest.php
index ea1e4a00871c6d27adecded459cf05447cefa5c7..80915415ebe120a1dfd495a9bbd52eae17e2b93f 100644
--- a/core/modules/jsonapi/tests/src/Functional/SearchPageTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/SearchPageTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "SearchPage" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class SearchPageTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/ShortcutSetTest.php b/core/modules/jsonapi/tests/src/Functional/ShortcutSetTest.php
index 8aeb8da4e8b852f607fda97ebbf34afa26e084c2..d1efcc2652b832e4d1781b820074b316b178350d 100644
--- a/core/modules/jsonapi/tests/src/Functional/ShortcutSetTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ShortcutSetTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "ShortcutSet" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ShortcutSetTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/UserTest.php b/core/modules/jsonapi/tests/src/Functional/UserTest.php
index f1b68d225309f4db345a2862be34d854183fb0ac..56090392619ea15f13dd468fd5e33fe6b6188c3f 100644
--- a/core/modules/jsonapi/tests/src/Functional/UserTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/UserTest.php
@@ -17,6 +17,7 @@
  * JSON:API integration test for the "User" content entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class UserTest extends ResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/ViewTest.php b/core/modules/jsonapi/tests/src/Functional/ViewTest.php
index a4c105aae9f266eeeeda3ce0dd59d608aa4d1607..e546d9496f22aff237c509340394ec5f6954bbd2 100644
--- a/core/modules/jsonapi/tests/src/Functional/ViewTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/ViewTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "View" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class ViewTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/VocabularyTest.php b/core/modules/jsonapi/tests/src/Functional/VocabularyTest.php
index cf86bb3e77b006fcd096a9d21ab3f03505488ffa..6c177d90b54f909aa49c62499b842b230b19f84a 100644
--- a/core/modules/jsonapi/tests/src/Functional/VocabularyTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/VocabularyTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "vocabulary" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class VocabularyTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Functional/WorkflowTest.php b/core/modules/jsonapi/tests/src/Functional/WorkflowTest.php
index 7a2a00a8e03dc0dd871b9634f657393145a4f625..c5d476372f9efb74564a6cd585f33dbda85afd5c 100644
--- a/core/modules/jsonapi/tests/src/Functional/WorkflowTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/WorkflowTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "Workflow" config entity type.
  *
  * @group jsonapi
+ * @group #slow
  */
 class WorkflowTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php b/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php
index b15896dc4ab277a0a175914fc27d9f82b85d1d49..f53992897980827a7eb98cb9ef53c2b691e85757 100644
--- a/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php
+++ b/core/modules/jsonapi/tests/src/Kernel/Context/FieldResolverTest.php
@@ -11,6 +11,7 @@
 /**
  * @coversDefaultClass \Drupal\jsonapi\Context\FieldResolver
  * @group jsonapi
+ * @group #slow
  *
  * @internal
  */
diff --git a/core/modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php b/core/modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php
index 928fdf8514aefe5c75275c52e78a1546ab3f23d0..5c061fe519f26626e744eef5e1813f9f73bf391a 100644
--- a/core/modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php
+++ b/core/modules/jsonapi/tests/src/Unit/Normalizer/HttpExceptionNormalizerTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\jsonapi\Unit\Normalizer;
 
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Config\ImmutableConfig;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\jsonapi\Normalizer\HttpExceptionNormalizer;
 use Drupal\Tests\UnitTestCase;
@@ -26,6 +28,11 @@ public function testNormalize() {
     $request_stack->getCurrentRequest()->willReturn(Request::create('http://localhost/'));
     $container = $this->prophesize(ContainerInterface::class);
     $container->get('request_stack')->willReturn($request_stack->reveal());
+    $config = $this->prophesize(ImmutableConfig::class);
+    $config->get('error_level')->willReturn(ERROR_REPORTING_DISPLAY_VERBOSE);
+    $config_factory = $this->prophesize(ConfigFactory::class);
+    $config_factory->get('system.logging')->willReturn($config->reveal());
+    $container->get('config.factory')->willReturn($config_factory->reveal());
     \Drupal::setContainer($container->reveal());
     $exception = new AccessDeniedHttpException('lorem', NULL, 13);
     $current_user = $this->prophesize(AccountInterface::class);
diff --git a/core/modules/language/config/schema/language.schema.yml b/core/modules/language/config/schema/language.schema.yml
index a1007417ee93ab12a1457339471727f554942e9a..2df46a5e4e2db6a831c5381f7f168e44fa88e414 100644
--- a/core/modules/language/config/schema/language.schema.yml
+++ b/core/modules/language/config/schema/language.schema.yml
@@ -135,11 +135,3 @@ condition.plugin.language:
       type: sequence
       sequence:
         type: langcode
-
-field.widget.settings.language_select:
-  type: mapping
-  label: 'Language format settings'
-  mapping:
-    include_locked:
-      type: boolean
-      label: 'Include locked languages'
diff --git a/core/modules/language/src/Form/NegotiationBrowserForm.php b/core/modules/language/src/Form/NegotiationBrowserForm.php
index 1ffd1a2c5c3fc8cda6f34d61b5a1c4d36a71b3a9..e0e50442de8a5e9c4d5f19cb60f79f3afa312fb2 100644
--- a/core/modules/language/src/Form/NegotiationBrowserForm.php
+++ b/core/modules/language/src/Form/NegotiationBrowserForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\language\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
@@ -26,8 +27,8 @@ class NegotiationBrowserForm extends ConfigFormBase {
   /**
    * {@inheritdoc}
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ConfigurableLanguageManagerInterface $language_manager) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->languageManager = $language_manager;
   }
 
@@ -37,6 +38,7 @@ public function __construct(ConfigFactoryInterface $config_factory, Configurable
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('language_manager')
     );
   }
diff --git a/core/modules/language/src/Form/NegotiationConfigureForm.php b/core/modules/language/src/Form/NegotiationConfigureForm.php
index 2b808d54d408d54d041f5801e91dfaf0e4ed83ee..d25305f0306ca6faaf15e575f87e14aedf1f29a4 100644
--- a/core/modules/language/src/Form/NegotiationConfigureForm.php
+++ b/core/modules/language/src/Form/NegotiationConfigureForm.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Block\BlockManagerInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Form\ConfigFormBase;
@@ -68,6 +69,8 @@ class NegotiationConfigureForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
    *   The language manager.
    * @param \Drupal\language\LanguageNegotiatorInterface $negotiator
@@ -79,8 +82,8 @@ class NegotiationConfigureForm extends ConfigFormBase {
    * @param \Drupal\Core\Entity\EntityStorageInterface $block_storage
    *   The block storage, or NULL if not available.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, BlockManagerInterface $block_manager, ThemeHandlerInterface $theme_handler, EntityStorageInterface $block_storage = NULL) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, BlockManagerInterface $block_manager, ThemeHandlerInterface $theme_handler, EntityStorageInterface $block_storage = NULL) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->languageTypes = $this->config('language.types');
     $this->languageManager = $language_manager;
     $this->negotiator = $negotiator;
@@ -97,6 +100,7 @@ public static function create(ContainerInterface $container) {
     $block_storage = $entity_type_manager->hasHandler('block', 'storage') ? $entity_type_manager->getStorage('block') : NULL;
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('language_manager'),
       $container->get('language_negotiator'),
       $container->get('plugin.manager.block'),
diff --git a/core/modules/language/src/Form/NegotiationUrlForm.php b/core/modules/language/src/Form/NegotiationUrlForm.php
index f0f0a39f0bb887534be8ed8245868ffd2c379afe..6cb54e4a322843cf722c2c03fcaf1f379f58017f 100644
--- a/core/modules/language/src/Form/NegotiationUrlForm.php
+++ b/core/modules/language/src/Form/NegotiationUrlForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\language\Form;
 
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
@@ -30,11 +31,13 @@ class NegotiationUrlForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, LanguageManagerInterface $language_manager) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->languageManager = $language_manager;
   }
 
@@ -44,6 +47,7 @@ public function __construct(ConfigFactoryInterface $config_factory, LanguageMana
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('language_manager')
     );
   }
diff --git a/core/modules/language/tests/src/Functional/GenericTest.php b/core/modules/language/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f0ee4822ab7d44ab55e9ea92cb58d1c5106c3816
--- /dev/null
+++ b/core/modules/language/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\language\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for language.
+ *
+ * @group language
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/language/tests/src/Functional/LanguageBreadcrumbTest.php b/core/modules/language/tests/src/Functional/LanguageBreadcrumbTest.php
index 21c9e5916b36d662ea3eb803d9d1dce4a4a42590..9c91eea834e39e60d382ea12486b46c785977426 100644
--- a/core/modules/language/tests/src/Functional/LanguageBreadcrumbTest.php
+++ b/core/modules/language/tests/src/Functional/LanguageBreadcrumbTest.php
@@ -48,7 +48,7 @@ public function testBreadCrumbs() {
     $this->assertBreadcrumb('de/user/login', []);
     $this->assertBreadcrumb('gsw-berne/user/login', []);
 
-    $admin_user = $this->drupalCreateUser(['access administration pages']);
+    $admin_user = $this->drupalCreateUser(['access administration pages', 'administer blocks']);
     $this->drupalLogin($admin_user);
 
     // Use administration routes to assert that breadcrumb is displayed
diff --git a/core/modules/language/tests/src/Kernel/ContentLanguageSettingsValidationTest.php b/core/modules/language/tests/src/Kernel/ContentLanguageSettingsValidationTest.php
index 07bab877aafb9005a72a496f43727e6a2c691f0a..ba245ad0b594b175c474c45a981c062ce015dfbc 100644
--- a/core/modules/language/tests/src/Kernel/ContentLanguageSettingsValidationTest.php
+++ b/core/modules/language/tests/src/Kernel/ContentLanguageSettingsValidationTest.php
@@ -9,6 +9,7 @@
  * Tests validation of content_language_settings entities.
  *
  * @group language
+ * @group #slow
  */
 class ContentLanguageSettingsValidationTest extends ConfigEntityValidationTestBase {
 
diff --git a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php
index 2cacadd2394adf3e0ef9bad6211dcc347b0b9df0..424f8d69105b258cfc2d6809ec88fca3edc67baf 100644
--- a/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php
+++ b/core/modules/language/tests/src/Kernel/Migrate/d7/MigrateLanguageContentTaxonomyVocabularySettingsTest.php
@@ -43,7 +43,7 @@ public function testLanguageContentTaxonomy() {
     $target_entity = 'taxonomy_term';
     // No multilingual options for terms, i18n_mode = 0.
     $this->assertLanguageContentSettings($target_entity, 'tags', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
-    $this->assertLanguageContentSettings($target_entity, 'forums', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
+    $this->assertLanguageContentSettings($target_entity, 'sujet_de_discussion', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
     $this->assertLanguageContentSettings($target_entity, 'vocabulary_name_much_longer_th', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
     $this->assertLanguageContentSettings($target_entity, 'test_vocabulary', LanguageInterface::LANGCODE_NOT_SPECIFIED, FALSE, ['enabled' => FALSE]);
     // Localize, i18n_mode = 1.
diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module
index ccbfaa968fbf327d71249564af2a1274b4fe9dd7..0036442aa5ffd633e8623a3ebc0fa0e566c9716a 100644
--- a/core/modules/layout_builder/layout_builder.module
+++ b/core/modules/layout_builder/layout_builder.module
@@ -444,7 +444,7 @@ function layout_builder_entity_view_display_presave(EntityViewDisplayInterface $
             continue;
           }
           if (!isset($formatter['settings']['tooltip']) || !isset($formatter['settings']['time_diff'])) {
-            @trigger_error("The 'timestamp' formatter plugin 'tooltip' and 'time_diff' settings were added in drupal:10.1.0 and will be mandatory in Drupal 11.0.0. See https://www.drupal.org/node/2993639", E_USER_DEPRECATED);
+            @trigger_error("Using the 'timestamp' formatter plugin  without the 'tooltip' and 'time_diff' settings is deprecated in drupal:10.1.0 and is required in drupal:11.0.0. See https://www.drupal.org/node/2993639", E_USER_DEPRECATED);
             $formatter['settings'] += $plugin_definition['class']::defaultSettings();
             // Existing timestamp formatters don't have tooltip.
             $formatter['settings']['tooltip']['date_format'] = '';
diff --git a/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php b/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php
index 9d553730fd46b13f3c35f7cdc61bf76bec3b2851..75eba575b752345ac65258ef277966a2fcd422a5 100644
--- a/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php
+++ b/core/modules/layout_builder/src/Form/LayoutRebuildConfirmFormBase.php
@@ -84,6 +84,10 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
       $form['actions']['cancel']['#attributes']['class'][] = 'dialog-cancel';
       $target_highlight_id = !empty($this->uuid) ? $this->blockUpdateHighlightId($this->uuid) : $this->sectionUpdateHighlightId($delta);
       $form['#attributes']['data-layout-builder-target-highlight-id'] = $target_highlight_id;
+      // The AJAX system automatically moves focus to the first tabbable
+      // element after closing a dialog, sometimes scrolling to a page top.
+      // Disable refocus on the button.
+      $form['actions']['submit']['#ajax']['disable-refocus'] = TRUE;
     }
 
     // Mark this as an administrative page for JavaScript ("Back to site" link).
diff --git a/core/modules/layout_builder/tests/src/Functional/GenericTest.php b/core/modules/layout_builder/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9fffe5b5d66ee5722c0e2480756cba86ee566d96
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for layout_builder.
+ *
+ * @group layout_builder
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderBlocksTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderBlocksTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..356fd196bc9b0ebd0624b0a80ef932de3c6e5664
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderBlocksTest.php
@@ -0,0 +1,282 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional;
+
+use Drupal\node\Entity\Node;
+use Drupal\views\Entity\View;
+
+/**
+ * Tests the Layout Builder UI with blocks.
+ *
+ * @group layout_builder
+ * @group #slow
+ */
+class LayoutBuilderBlocksTest extends LayoutBuilderTestBase {
+
+  /**
+   * Tests that block plugins can define custom attributes and contextual links.
+   */
+  public function testPluginsProvidingCustomAttributesAndContextualLinks() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'access contextual links',
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+    $page->clickLink('Manage layout');
+    $page->clickLink('Add section');
+    $page->clickLink('Layout Builder Test Plugin');
+    $page->pressButton('Add section');
+    $page->clickLink('Add block');
+    $page->clickLink('Test Attributes');
+    $page->pressButton('Add block');
+    $page->pressButton('Save layout');
+
+    $this->drupalGet('node/1');
+
+    $assert_session->elementExists('css', '.attribute-test-class');
+    $assert_session->elementExists('css', '[custom-attribute=test]');
+    $assert_session->elementExists('css', 'div[data-contextual-id*="layout_builder_test"]');
+  }
+
+  /**
+   * Tests preview-aware layout & block plugins.
+   */
+  public function testPreviewAwarePlugins() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+    $page->clickLink('Manage layout');
+    $page->clickLink('Add section');
+    $page->clickLink('Layout Builder Test Plugin');
+    $page->pressButton('Add section');
+    $page->clickLink('Add block');
+    $page->clickLink('Preview-aware block');
+    $page->pressButton('Add block');
+
+    $assert_session->elementExists('css', '.go-birds-preview');
+    $assert_session->pageTextContains('The block template is being previewed.');
+    $assert_session->pageTextContains('This block is being rendered in preview mode.');
+
+    $page->pressButton('Save layout');
+    $this->drupalGet('node/1');
+
+    $assert_session->elementNotExists('css', '.go-birds-preview');
+    $assert_session->pageTextNotContains('The block template is being previewed.');
+    $assert_session->pageTextContains('This block is being rendered normally.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function testLayoutBuilderChooseBlocksAlter() {
+    // See layout_builder_test_plugin_filter_block__layout_builder_alter().
+    $assert_session = $this->assertSession();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+      'administer node fields',
+    ]));
+
+    // From the manage display page, go to manage the layout.
+    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+    $assert_session->linkExists('Manage layout');
+    $this->clickLink('Manage layout');
+
+    // Add a new block.
+    $this->clickLink('Add block');
+
+    // Verify that blocks not modified are present.
+    $assert_session->linkExists('Powered by Drupal');
+    $assert_session->linkExists('Default revision');
+
+    // Verify that blocks explicitly removed are not present.
+    $assert_session->linkNotExists('Help');
+    $assert_session->linkNotExists('Sticky at top of lists');
+    $assert_session->linkNotExists('Main page content');
+    $assert_session->linkNotExists('Page title');
+    $assert_session->linkNotExists('Messages');
+    $assert_session->linkNotExists('Help');
+    $assert_session->linkNotExists('Tabs');
+    $assert_session->linkNotExists('Primary admin actions');
+
+    // Verify that Changed block is not present on first section.
+    $assert_session->linkNotExists('Changed');
+
+    // Go back to Manage layout.
+    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
+    $this->clickLink('Manage layout');
+
+    // Add a new section.
+    $this->clickLink('Add section', 1);
+    $assert_session->linkExists('Two column');
+    $this->clickLink('Two column');
+    $assert_session->buttonExists('Add section');
+    $this->getSession()->getPage()->pressButton('Add section');
+    // Add a new block to second section.
+    $this->clickLink('Add block', 1);
+
+    // Verify that Changed block is present on second section.
+    $assert_session->linkExists('Changed');
+  }
+
+  /**
+   * Tests that deleting a View block used in Layout Builder works.
+   */
+  public function testDeletedView() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+    // Enable overrides.
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['layout[allow_custom]' => TRUE], 'Save');
+    $this->drupalGet('node/1');
+
+    $assert_session->linkExists('Layout');
+    $this->clickLink('Layout');
+    $this->clickLink('Add block');
+    $this->clickLink('Test Block View');
+    $page->pressButton('Add block');
+
+    $assert_session->pageTextContains('Test Block View');
+    $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
+    $page->pressButton('Save');
+    $assert_session->pageTextContains('Test Block View');
+    $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
+
+    View::load('test_block_view')->delete();
+    $this->drupalGet('node/1');
+    // Node can be loaded after deleting the View.
+    $assert_session->pageTextContains(Node::load(1)->getTitle());
+    $assert_session->pageTextNotContains('Test Block View');
+  }
+
+  /**
+   * Tests the usage of placeholders for empty blocks.
+   *
+   * @see \Drupal\Core\Render\PreviewFallbackInterface::getPreviewFallbackString()
+   * @see \Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray::onBuildRender()
+   */
+  public function testBlockPlaceholder() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+
+    // Customize the default view mode.
+    $this->drupalGet("$field_ui_prefix/display/default/layout");
+
+    // Add a block whose content is controlled by state and is empty by default.
+    $this->clickLink('Add block');
+    $this->clickLink('Test block caching');
+    $page->fillField('settings[label]', 'The block label');
+    $page->pressButton('Add block');
+
+    $block_content = 'I am content';
+    $placeholder_content = 'Placeholder for the "The block label" block';
+
+    // The block placeholder is displayed and there is no content.
+    $assert_session->pageTextContains($placeholder_content);
+    $assert_session->pageTextNotContains($block_content);
+
+    // Set block content and reload the page.
+    \Drupal::state()->set('block_test.content', $block_content);
+    $this->getSession()->reload();
+
+    // The block placeholder is no longer displayed and the content is visible.
+    $assert_session->pageTextNotContains($placeholder_content);
+    $assert_session->pageTextContains($block_content);
+  }
+
+  /**
+   * Tests the ability to use a specified block label for field blocks.
+   */
+  public function testFieldBlockLabel() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+    $this->drupalGet("$field_ui_prefix/display/default");
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+
+    // Customize the default view mode.
+    $this->drupalGet("$field_ui_prefix/display/default/layout");
+
+    // Add a body block whose label will be overridden.
+    $this->clickLink('Add block');
+    $this->clickLink('Body');
+
+    // Enable the Label Display and set the Label to a modified field
+    // block label.
+    $modified_field_block_label = 'Modified Field Block Label';
+    $page->checkField('settings[label_display]');
+    $page->fillField('settings[label]', $modified_field_block_label);
+
+    // Save the block and layout.
+    $page->pressButton('Add block');
+    $page->pressButton('Save layout');
+
+    // Revisit the default layout view mode page.
+    $this->drupalGet("$field_ui_prefix/display/default/layout");
+
+    // The modified field block label is displayed.
+    $assert_session->pageTextContains($modified_field_block_label);
+  }
+
+  /**
+   * Tests the Block UI when Layout Builder is installed.
+   */
+  public function testBlockUiListing() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'administer blocks',
+    ]));
+
+    $this->drupalGet('admin/structure/block');
+    $page->clickLink('Place block');
+
+    // Ensure that blocks expected to appear are available.
+    $assert_session->pageTextContains('Test HTML block');
+    $assert_session->pageTextContains('Block test');
+    // Ensure that blocks not expected to appear are not available.
+    $assert_session->pageTextNotContains('Body');
+    $assert_session->pageTextNotContains('Content fields');
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
index 296ab0c4a7c9a2cce5f4775fed15a51a4fa181ab..ea8daa841a05a91e6498311b81b6be4d3376dc87 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
@@ -5,72 +5,14 @@
 use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
 use Drupal\layout_builder\Section;
 use Drupal\node\Entity\Node;
-use Drupal\Tests\BrowserTestBase;
-use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
-use Drupal\views\Entity\View;
 
 /**
  * Tests the Layout Builder UI.
  *
  * @group layout_builder
+ * @group #slow
  */
-class LayoutBuilderTest extends BrowserTestBase {
-
-  use FieldUiTestTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = [
-    'field_ui',
-    'views',
-    'layout_builder',
-    'layout_builder_views_test',
-    'layout_test',
-    'block',
-    'block_test',
-    'contextual',
-    'node',
-    'layout_builder_test',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $defaultTheme = 'starterkit_theme';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    $this->drupalPlaceBlock('local_tasks_block');
-
-    // Create two nodes.
-    $this->createContentType([
-      'type' => 'bundle_with_section_field',
-      'name' => 'Bundle with section field',
-    ]);
-    $this->createNode([
-      'type' => 'bundle_with_section_field',
-      'title' => 'The first node title',
-      'body' => [
-        [
-          'value' => 'The first node body',
-        ],
-      ],
-    ]);
-    $this->createNode([
-      'type' => 'bundle_with_section_field',
-      'title' => 'The second node title',
-      'body' => [
-        [
-          'value' => 'The second node body',
-        ],
-      ],
-    ]);
-  }
+class LayoutBuilderTest extends LayoutBuilderTestBase {
 
   /**
    * Tests deleting a field in-use by an overridden layout.
@@ -528,65 +470,6 @@ public function testAccess() {
     $assert_session->pageTextContains('Access denied');
   }
 
-  /**
-   * Tests that a non-default view mode works as expected.
-   */
-  public function testNonDefaultViewMode() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'configure any layout',
-      'administer node display',
-    ]));
-
-    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
-    // Allow overrides for the layout.
-    $this->drupalGet("$field_ui_prefix/display/default");
-    $page->checkField('layout[enabled]');
-    $page->pressButton('Save');
-    $page->checkField('layout[allow_custom]');
-    $page->pressButton('Save');
-
-    $this->clickLink('Manage layout');
-    // Confirm the body field only is shown once.
-    $assert_session->elementsCount('css', '.field--name-body', 1);
-    $page->pressButton('Discard changes');
-    $page->pressButton('Confirm');
-
-    $this->clickLink('Teaser');
-    // Enabling Layout Builder for the default mode does not affect the teaser.
-    $assert_session->addressEquals("$field_ui_prefix/display/teaser");
-    $assert_session->elementNotExists('css', '#layout-builder__layout');
-    $assert_session->checkboxNotChecked('layout[enabled]');
-    $page->checkField('layout[enabled]');
-    $page->pressButton('Save');
-    $assert_session->linkExists('Manage layout');
-    $page->clickLink('Manage layout');
-    // Confirm the body field only is shown once.
-    $assert_session->elementsCount('css', '.field--name-body', 1);
-
-    // Enable a disabled view mode.
-    $page->pressButton('Discard changes');
-    $page->pressButton('Confirm');
-    $assert_session->addressEquals("$field_ui_prefix/display/teaser");
-    $page->clickLink('Default');
-    $assert_session->addressEquals("$field_ui_prefix/display");
-    $assert_session->linkNotExists('Full content');
-    $page->checkField('display_modes_custom[full]');
-    $page->pressButton('Save');
-
-    $assert_session->linkExists('Full content');
-    $page->clickLink('Full content');
-    $assert_session->addressEquals("$field_ui_prefix/display/full");
-    $page->checkField('layout[enabled]');
-    $page->pressButton('Save');
-    $assert_session->linkExists('Manage layout');
-    $page->clickLink('Manage layout');
-    // Confirm the body field only is shown once.
-    $assert_session->elementsCount('css', '.field--name-body', 1);
-  }
-
   /**
    * Tests that component's dependencies are respected during removal.
    */
@@ -665,71 +548,6 @@ public function testPluginDependencies() {
     $assert_session->elementNotExists('css', '.block.menu--my-menu');
   }
 
-  /**
-   * Tests that block plugins can define custom attributes and contextual links.
-   */
-  public function testPluginsProvidingCustomAttributesAndContextualLinks() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'access contextual links',
-      'configure any layout',
-      'administer node display',
-    ]));
-
-    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
-    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
-    $page->clickLink('Manage layout');
-    $page->clickLink('Add section');
-    $page->clickLink('Layout Builder Test Plugin');
-    $page->pressButton('Add section');
-    $page->clickLink('Add block');
-    $page->clickLink('Test Attributes');
-    $page->pressButton('Add block');
-    $page->pressButton('Save layout');
-
-    $this->drupalGet('node/1');
-
-    $assert_session->elementExists('css', '.attribute-test-class');
-    $assert_session->elementExists('css', '[custom-attribute=test]');
-    $assert_session->elementExists('css', 'div[data-contextual-id*="layout_builder_test"]');
-  }
-
-  /**
-   * Tests preview-aware layout & block plugins.
-   */
-  public function testPreviewAwarePlugins() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'configure any layout',
-      'administer node display',
-    ]));
-
-    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
-    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
-    $page->clickLink('Manage layout');
-    $page->clickLink('Add section');
-    $page->clickLink('Layout Builder Test Plugin');
-    $page->pressButton('Add section');
-    $page->clickLink('Add block');
-    $page->clickLink('Preview-aware block');
-    $page->pressButton('Add block');
-
-    $assert_session->elementExists('css', '.go-birds-preview');
-    $assert_session->pageTextContains('The block template is being previewed.');
-    $assert_session->pageTextContains('This block is being rendered in preview mode.');
-
-    $page->pressButton('Save layout');
-    $this->drupalGet('node/1');
-
-    $assert_session->elementNotExists('css', '.go-birds-preview');
-    $assert_session->pageTextNotContains('The block template is being previewed.');
-    $assert_session->pageTextContains('This block is being rendered normally.');
-  }
-
   /**
    * Tests preview-aware templates.
    */
@@ -760,270 +578,6 @@ public function testPreviewAwareTemplates() {
     $assert_session->pageTextNotContains('This is a preview, indeed');
   }
 
-  /**
-   * Tests the interaction between full and default view modes.
-   *
-   * @see \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getDefaultSectionStorage()
-   */
-  public function testLayoutBuilderUiFullViewMode() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'configure any layout',
-      'administer node display',
-      'administer node fields',
-    ]));
-
-    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
-
-    // For the purposes of this test, turn the full view mode on and off to
-    // prevent copying from the customized default view mode.
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['display_modes_custom[full]' => TRUE], 'Save');
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['display_modes_custom[full]' => FALSE], 'Save');
-
-    // Allow overrides for the layout.
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['layout[allow_custom]' => TRUE], 'Save');
-
-    // Customize the default view mode.
-    $this->drupalGet("$field_ui_prefix/display/default/layout");
-    $this->clickLink('Add block');
-    $this->clickLink('Powered by Drupal');
-    $page->fillField('settings[label]', 'This is the default view mode');
-    $page->checkField('settings[label_display]');
-    $page->pressButton('Add block');
-    $assert_session->pageTextContains('This is the default view mode');
-    $page->pressButton('Save layout');
-
-    // The default view mode is used for both the node display and layout UI.
-    $this->drupalGet('node/1');
-    $assert_session->pageTextContains('This is the default view mode');
-    $assert_session->pageTextNotContains('This is the full view mode');
-    $this->drupalGet('node/1/layout');
-    $assert_session->pageTextContains('This is the default view mode');
-    $assert_session->pageTextNotContains('This is the full view mode');
-    $page->pressButton('Discard changes');
-    $page->pressButton('Confirm');
-
-    // Enable the full view mode and customize it.
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['display_modes_custom[full]' => TRUE], 'Save');
-    $this->drupalGet("{$field_ui_prefix}/display/full");
-    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
-    $this->drupalGet("{$field_ui_prefix}/display/full");
-    $this->submitForm(['layout[allow_custom]' => TRUE], 'Save');
-    $this->drupalGet("$field_ui_prefix/display/full/layout");
-    $this->clickLink('Add block');
-    $this->clickLink('Powered by Drupal');
-    $page->fillField('settings[label]', 'This is the full view mode');
-    $page->checkField('settings[label_display]');
-    $page->pressButton('Add block');
-    $assert_session->pageTextContains('This is the full view mode');
-    $page->pressButton('Save layout');
-
-    // The full view mode is now used for both the node display and layout UI.
-    $this->drupalGet('node/1');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-    $this->drupalGet('node/1/layout');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-    $page->pressButton('Discard changes');
-    $page->pressButton('Confirm');
-
-    // Disable the full view mode, the default should be used again.
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['display_modes_custom[full]' => FALSE], 'Save');
-    $this->drupalGet('node/1');
-    $assert_session->pageTextContains('This is the default view mode');
-    $assert_session->pageTextNotContains('This is the full view mode');
-    $this->drupalGet('node/1/layout');
-    $assert_session->pageTextContains('This is the default view mode');
-    $assert_session->pageTextNotContains('This is the full view mode');
-    $page->pressButton('Discard changes');
-    $page->pressButton('Confirm');
-
-    // Re-enabling the full view mode restores the layout changes.
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['display_modes_custom[full]' => TRUE], 'Save');
-    $this->drupalGet('node/1');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-    $this->drupalGet('node/1/layout');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-
-    // Create an override of the full view mode.
-    $this->clickLink('Add block');
-    $this->clickLink('Powered by Drupal');
-    $page->fillField('settings[label]', 'This is an override of the full view mode');
-    $page->checkField('settings[label_display]');
-    $page->pressButton('Add block');
-    $assert_session->pageTextContains('This is an override of the full view mode');
-    $page->pressButton('Save layout');
-
-    $this->drupalGet('node/1');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextContains('This is an override of the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-    $this->drupalGet('node/1/layout');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextContains('This is an override of the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-    $page->pressButton('Discard changes');
-    $page->pressButton('Confirm');
-
-    // The override does not affect the full view mode.
-    $this->drupalGet("$field_ui_prefix/display/full/layout");
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextNotContains('This is an override of the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-
-    // Reverting the override restores back to the full view mode.
-    $this->drupalGet('node/1/layout');
-    $page->pressButton('Revert to default');
-    $page->pressButton('Revert');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextNotContains('This is an override of the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-    $this->drupalGet('node/1/layout');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextNotContains('This is an override of the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-
-    // Recreate an override of the full view mode.
-    $this->clickLink('Add block');
-    $this->clickLink('Powered by Drupal');
-    $page->fillField('settings[label]', 'This is an override of the full view mode');
-    $page->checkField('settings[label_display]');
-    $page->pressButton('Add block');
-    $assert_session->pageTextContains('This is an override of the full view mode');
-    $page->pressButton('Save layout');
-
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextContains('This is an override of the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-    $this->drupalGet('node/1/layout');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextContains('This is an override of the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-    $page->pressButton('Discard changes');
-    $page->pressButton('Confirm');
-
-    // Disable the full view mode.
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['display_modes_custom[full]' => FALSE], 'Save');
-
-    // The override of the full view mode is still available.
-    $this->drupalGet('node/1');
-    $assert_session->pageTextContains('This is the full view mode');
-    $assert_session->pageTextContains('This is an override of the full view mode');
-    $assert_session->pageTextNotContains('This is the default view mode');
-
-    // Reverting the override restores back to the default view mode.
-    $this->drupalGet('node/1/layout');
-    $page->pressButton('Revert to default');
-    $page->pressButton('Revert');
-    $assert_session->pageTextContains('This is the default view mode');
-    $assert_session->pageTextNotContains('This is the full view mode');
-    $this->drupalGet('node/1/layout');
-    $assert_session->pageTextContains('This is the default view mode');
-    $assert_session->pageTextNotContains('This is the full view mode');
-    $page->pressButton('Discard changes');
-    $page->pressButton('Confirm');
-  }
-
-  /**
-   * Ensures that one bundle doesn't interfere with another bundle.
-   */
-  public function testFullViewModeMultipleBundles() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'configure any layout',
-      'administer node display',
-    ]));
-
-    // Create one bundle with the full view mode enabled.
-    $this->createContentType(['type' => 'full_bundle']);
-    $this->drupalGet('admin/structure/types/manage/full_bundle/display/default');
-    $page->checkField('display_modes_custom[full]');
-    $page->pressButton('Save');
-
-    // Create another bundle without the full view mode enabled.
-    $this->createContentType(['type' => 'default_bundle']);
-    $this->drupalGet('admin/structure/types/manage/default_bundle/display/default');
-
-    // Enable Layout Builder for defaults and overrides.
-    $page->checkField('layout[enabled]');
-    $page->pressButton('Save');
-    $page->checkField('layout[allow_custom]');
-    $page->pressButton('Save');
-    $assert_session->checkboxChecked('layout[allow_custom]');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function testLayoutBuilderChooseBlocksAlter() {
-    // See layout_builder_test_plugin_filter_block__layout_builder_alter().
-    $assert_session = $this->assertSession();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'configure any layout',
-      'administer node display',
-      'administer node fields',
-    ]));
-
-    // From the manage display page, go to manage the layout.
-    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
-    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
-    $assert_session->linkExists('Manage layout');
-    $this->clickLink('Manage layout');
-
-    // Add a new block.
-    $this->clickLink('Add block');
-
-    // Verify that blocks not modified are present.
-    $assert_session->linkExists('Powered by Drupal');
-    $assert_session->linkExists('Default revision');
-
-    // Verify that blocks explicitly removed are not present.
-    $assert_session->linkNotExists('Help');
-    $assert_session->linkNotExists('Sticky at top of lists');
-    $assert_session->linkNotExists('Main page content');
-    $assert_session->linkNotExists('Page title');
-    $assert_session->linkNotExists('Messages');
-    $assert_session->linkNotExists('Help');
-    $assert_session->linkNotExists('Tabs');
-    $assert_session->linkNotExists('Primary admin actions');
-
-    // Verify that Changed block is not present on first section.
-    $assert_session->linkNotExists('Changed');
-
-    // Go back to Manage layout.
-    $this->drupalGet('admin/structure/types/manage/bundle_with_section_field/display/default');
-    $this->clickLink('Manage layout');
-
-    // Add a new section.
-    $this->clickLink('Add section', 1);
-    $assert_session->linkExists('Two column');
-    $this->clickLink('Two column');
-    $assert_session->buttonExists('Add section');
-    $this->getSession()->getPage()->pressButton('Add section');
-    // Add a new block to second section.
-    $this->clickLink('Add block', 1);
-
-    // Verify that Changed block is present on second section.
-    $assert_session->linkExists('Changed');
-  }
-
   /**
    * Tests that extra fields work before and after enabling Layout Builder.
    */
@@ -1103,45 +657,6 @@ public function testPendingRevision() {
     $assert_session->pageTextContains('The pending title of the first node');
   }
 
-  /**
-   * Tests that deleting a View block used in Layout Builder works.
-   */
-  public function testDeletedView() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'configure any layout',
-      'administer node display',
-    ]));
-
-    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
-    // Enable overrides.
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['layout[allow_custom]' => TRUE], 'Save');
-    $this->drupalGet('node/1');
-
-    $assert_session->linkExists('Layout');
-    $this->clickLink('Layout');
-    $this->clickLink('Add block');
-    $this->clickLink('Test Block View');
-    $page->pressButton('Add block');
-
-    $assert_session->pageTextContains('Test Block View');
-    $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
-    $page->pressButton('Save');
-    $assert_session->pageTextContains('Test Block View');
-    $assert_session->elementExists('css', '.block-views-blocktest-block-view-block-1');
-
-    View::load('test_block_view')->delete();
-    $this->drupalGet('node/1');
-    // Node can be loaded after deleting the View.
-    $assert_session->pageTextContains(Node::load(1)->getTitle());
-    $assert_session->pageTextNotContains('Test Block View');
-  }
-
   /**
    * Tests that hook_form_alter() has access to the Layout Builder info.
    */
@@ -1260,90 +775,6 @@ public function testCustomSectionAttributes() {
     $assert_session->elementExists('css', '.go-birds');
   }
 
-  /**
-   * Tests the usage of placeholders for empty blocks.
-   *
-   * @see \Drupal\Core\Render\PreviewFallbackInterface::getPreviewFallbackString()
-   * @see \Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray::onBuildRender()
-   */
-  public function testBlockPlaceholder() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'configure any layout',
-      'administer node display',
-    ]));
-
-    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
-    $this->drupalGet("{$field_ui_prefix}/display/default");
-    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
-
-    // Customize the default view mode.
-    $this->drupalGet("$field_ui_prefix/display/default/layout");
-
-    // Add a block whose content is controlled by state and is empty by default.
-    $this->clickLink('Add block');
-    $this->clickLink('Test block caching');
-    $page->fillField('settings[label]', 'The block label');
-    $page->pressButton('Add block');
-
-    $block_content = 'I am content';
-    $placeholder_content = 'Placeholder for the "The block label" block';
-
-    // The block placeholder is displayed and there is no content.
-    $assert_session->pageTextContains($placeholder_content);
-    $assert_session->pageTextNotContains($block_content);
-
-    // Set block content and reload the page.
-    \Drupal::state()->set('block_test.content', $block_content);
-    $this->getSession()->reload();
-
-    // The block placeholder is no longer displayed and the content is visible.
-    $assert_session->pageTextNotContains($placeholder_content);
-    $assert_session->pageTextContains($block_content);
-  }
-
-  /**
-   * Tests the ability to use a specified block label for field blocks.
-   */
-  public function testFieldBlockLabel() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'configure any layout',
-      'administer node display',
-    ]));
-
-    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
-    $this->drupalGet("$field_ui_prefix/display/default");
-    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
-
-    // Customize the default view mode.
-    $this->drupalGet("$field_ui_prefix/display/default/layout");
-
-    // Add a body block whose label will be overridden.
-    $this->clickLink('Add block');
-    $this->clickLink('Body');
-
-    // Enable the Label Display and set the Label to a modified field
-    // block label.
-    $modified_field_block_label = 'Modified Field Block Label';
-    $page->checkField('settings[label_display]');
-    $page->fillField('settings[label]', $modified_field_block_label);
-
-    // Save the block and layout.
-    $page->pressButton('Add block');
-    $page->pressButton('Save layout');
-
-    // Revisit the default layout view mode page.
-    $this->drupalGet("$field_ui_prefix/display/default/layout");
-
-    // The modified field block label is displayed.
-    $assert_session->pageTextContains($modified_field_block_label);
-  }
-
   /**
    * Tests a custom alter of the overrides form.
    */
@@ -1379,28 +810,6 @@ public function testOverridesFormAlter() {
     $assert_session->pageTextContains('The layout override has been saved.');
   }
 
-  /**
-   * Tests the Block UI when Layout Builder is installed.
-   */
-  public function testBlockUiListing() {
-    $assert_session = $this->assertSession();
-    $page = $this->getSession()->getPage();
-
-    $this->drupalLogin($this->drupalCreateUser([
-      'administer blocks',
-    ]));
-
-    $this->drupalGet('admin/structure/block');
-    $page->clickLink('Place block');
-
-    // Ensure that blocks expected to appear are available.
-    $assert_session->pageTextContains('Test HTML block');
-    $assert_session->pageTextContains('Block test');
-    // Ensure that blocks not expected to appear are not available.
-    $assert_session->pageTextNotContains('Body');
-    $assert_session->pageTextNotContains('Content fields');
-  }
-
   /**
    * Tests the expected breadcrumbs of the Layout Builder UI.
    */
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTestBase.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..5ffe10fce9ffbaee101e9e614901fb492c3cca6e
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTestBase.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
+
+/**
+ * Tests the Layout Builder UI.
+ */
+class LayoutBuilderTestBase extends BrowserTestBase {
+
+  use FieldUiTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'field_ui',
+    'views',
+    'layout_builder',
+    'layout_builder_views_test',
+    'layout_test',
+    'block',
+    'block_test',
+    'contextual',
+    'node',
+    'layout_builder_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'starterkit_theme';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->drupalPlaceBlock('local_tasks_block');
+
+    // Create two nodes.
+    $this->createContentType([
+      'type' => 'bundle_with_section_field',
+      'name' => 'Bundle with section field',
+    ]);
+    $this->createNode([
+      'type' => 'bundle_with_section_field',
+      'title' => 'The first node title',
+      'body' => [
+        [
+          'value' => 'The first node body',
+        ],
+      ],
+    ]);
+    $this->createNode([
+      'type' => 'bundle_with_section_field',
+      'title' => 'The second node title',
+      'body' => [
+        [
+          'value' => 'The second node body',
+        ],
+      ],
+    ]);
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderViewModeTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderViewModeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bfc1f345e4f6ba28d18d7032efeddec231ab504a
--- /dev/null
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderViewModeTest.php
@@ -0,0 +1,280 @@
+<?php
+
+namespace Drupal\Tests\layout_builder\Functional;
+
+/**
+ * Tests the Layout Builder UI with view modes.
+ *
+ * @group layout_builder
+ * @group #slow
+ */
+class LayoutBuilderViewModeTest extends LayoutBuilderTestBase {
+
+  /**
+   * Tests that a non-default view mode works as expected.
+   */
+  public function testNonDefaultViewMode() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+    // Allow overrides for the layout.
+    $this->drupalGet("$field_ui_prefix/display/default");
+    $page->checkField('layout[enabled]');
+    $page->pressButton('Save');
+    $page->checkField('layout[allow_custom]');
+    $page->pressButton('Save');
+
+    $this->clickLink('Manage layout');
+    // Confirm the body field only is shown once.
+    $assert_session->elementsCount('css', '.field--name-body', 1);
+    $page->pressButton('Discard changes');
+    $page->pressButton('Confirm');
+
+    $this->clickLink('Teaser');
+    // Enabling Layout Builder for the default mode does not affect the teaser.
+    $assert_session->addressEquals("$field_ui_prefix/display/teaser");
+    $assert_session->elementNotExists('css', '#layout-builder__layout');
+    $assert_session->checkboxNotChecked('layout[enabled]');
+    $page->checkField('layout[enabled]');
+    $page->pressButton('Save');
+    $assert_session->linkExists('Manage layout');
+    $page->clickLink('Manage layout');
+    // Confirm the body field only is shown once.
+    $assert_session->elementsCount('css', '.field--name-body', 1);
+
+    // Enable a disabled view mode.
+    $page->pressButton('Discard changes');
+    $page->pressButton('Confirm');
+    $assert_session->addressEquals("$field_ui_prefix/display/teaser");
+    $page->clickLink('Default');
+    $assert_session->addressEquals("$field_ui_prefix/display");
+    $assert_session->linkNotExists('Full content');
+    $page->checkField('display_modes_custom[full]');
+    $page->pressButton('Save');
+
+    $assert_session->linkExists('Full content');
+    $page->clickLink('Full content');
+    $assert_session->addressEquals("$field_ui_prefix/display/full");
+    $page->checkField('layout[enabled]');
+    $page->pressButton('Save');
+    $assert_session->linkExists('Manage layout');
+    $page->clickLink('Manage layout');
+    // Confirm the body field only is shown once.
+    $assert_session->elementsCount('css', '.field--name-body', 1);
+  }
+
+  /**
+   * Tests the interaction between full and default view modes.
+   *
+   * @see \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getDefaultSectionStorage()
+   */
+  public function testLayoutBuilderUiFullViewMode() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+      'administer node fields',
+    ]));
+
+    $field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
+
+    // For the purposes of this test, turn the full view mode on and off to
+    // prevent copying from the customized default view mode.
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['display_modes_custom[full]' => TRUE], 'Save');
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['display_modes_custom[full]' => FALSE], 'Save');
+
+    // Allow overrides for the layout.
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['layout[allow_custom]' => TRUE], 'Save');
+
+    // Customize the default view mode.
+    $this->drupalGet("$field_ui_prefix/display/default/layout");
+    $this->clickLink('Add block');
+    $this->clickLink('Powered by Drupal');
+    $page->fillField('settings[label]', 'This is the default view mode');
+    $page->checkField('settings[label_display]');
+    $page->pressButton('Add block');
+    $assert_session->pageTextContains('This is the default view mode');
+    $page->pressButton('Save layout');
+
+    // The default view mode is used for both the node display and layout UI.
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('This is the default view mode');
+    $assert_session->pageTextNotContains('This is the full view mode');
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('This is the default view mode');
+    $assert_session->pageTextNotContains('This is the full view mode');
+    $page->pressButton('Discard changes');
+    $page->pressButton('Confirm');
+
+    // Enable the full view mode and customize it.
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['display_modes_custom[full]' => TRUE], 'Save');
+    $this->drupalGet("{$field_ui_prefix}/display/full");
+    $this->submitForm(['layout[enabled]' => TRUE], 'Save');
+    $this->drupalGet("{$field_ui_prefix}/display/full");
+    $this->submitForm(['layout[allow_custom]' => TRUE], 'Save');
+    $this->drupalGet("$field_ui_prefix/display/full/layout");
+    $this->clickLink('Add block');
+    $this->clickLink('Powered by Drupal');
+    $page->fillField('settings[label]', 'This is the full view mode');
+    $page->checkField('settings[label_display]');
+    $page->pressButton('Add block');
+    $assert_session->pageTextContains('This is the full view mode');
+    $page->pressButton('Save layout');
+
+    // The full view mode is now used for both the node display and layout UI.
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+    $page->pressButton('Discard changes');
+    $page->pressButton('Confirm');
+
+    // Disable the full view mode, the default should be used again.
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['display_modes_custom[full]' => FALSE], 'Save');
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('This is the default view mode');
+    $assert_session->pageTextNotContains('This is the full view mode');
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('This is the default view mode');
+    $assert_session->pageTextNotContains('This is the full view mode');
+    $page->pressButton('Discard changes');
+    $page->pressButton('Confirm');
+
+    // Re-enabling the full view mode restores the layout changes.
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['display_modes_custom[full]' => TRUE], 'Save');
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+
+    // Create an override of the full view mode.
+    $this->clickLink('Add block');
+    $this->clickLink('Powered by Drupal');
+    $page->fillField('settings[label]', 'This is an override of the full view mode');
+    $page->checkField('settings[label_display]');
+    $page->pressButton('Add block');
+    $assert_session->pageTextContains('This is an override of the full view mode');
+    $page->pressButton('Save layout');
+
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextContains('This is an override of the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextContains('This is an override of the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+    $page->pressButton('Discard changes');
+    $page->pressButton('Confirm');
+
+    // The override does not affect the full view mode.
+    $this->drupalGet("$field_ui_prefix/display/full/layout");
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextNotContains('This is an override of the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+
+    // Reverting the override restores back to the full view mode.
+    $this->drupalGet('node/1/layout');
+    $page->pressButton('Revert to default');
+    $page->pressButton('Revert');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextNotContains('This is an override of the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextNotContains('This is an override of the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+
+    // Recreate an override of the full view mode.
+    $this->clickLink('Add block');
+    $this->clickLink('Powered by Drupal');
+    $page->fillField('settings[label]', 'This is an override of the full view mode');
+    $page->checkField('settings[label_display]');
+    $page->pressButton('Add block');
+    $assert_session->pageTextContains('This is an override of the full view mode');
+    $page->pressButton('Save layout');
+
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextContains('This is an override of the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextContains('This is an override of the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+    $page->pressButton('Discard changes');
+    $page->pressButton('Confirm');
+
+    // Disable the full view mode.
+    $this->drupalGet("{$field_ui_prefix}/display/default");
+    $this->submitForm(['display_modes_custom[full]' => FALSE], 'Save');
+
+    // The override of the full view mode is still available.
+    $this->drupalGet('node/1');
+    $assert_session->pageTextContains('This is the full view mode');
+    $assert_session->pageTextContains('This is an override of the full view mode');
+    $assert_session->pageTextNotContains('This is the default view mode');
+
+    // Reverting the override restores back to the default view mode.
+    $this->drupalGet('node/1/layout');
+    $page->pressButton('Revert to default');
+    $page->pressButton('Revert');
+    $assert_session->pageTextContains('This is the default view mode');
+    $assert_session->pageTextNotContains('This is the full view mode');
+    $this->drupalGet('node/1/layout');
+    $assert_session->pageTextContains('This is the default view mode');
+    $assert_session->pageTextNotContains('This is the full view mode');
+    $page->pressButton('Discard changes');
+    $page->pressButton('Confirm');
+  }
+
+  /**
+   * Ensures that one bundle doesn't interfere with another bundle.
+   */
+  public function testFullViewModeMultipleBundles() {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'configure any layout',
+      'administer node display',
+    ]));
+
+    // Create one bundle with the full view mode enabled.
+    $this->createContentType(['type' => 'full_bundle']);
+    $this->drupalGet('admin/structure/types/manage/full_bundle/display/default');
+    $page->checkField('display_modes_custom[full]');
+    $page->pressButton('Save');
+
+    // Create another bundle without the full view mode enabled.
+    $this->createContentType(['type' => 'default_bundle']);
+    $this->drupalGet('admin/structure/types/manage/default_bundle/display/default');
+
+    // Enable Layout Builder for defaults and overrides.
+    $page->checkField('layout[enabled]');
+    $page->pressButton('Save');
+    $page->checkField('layout[allow_custom]');
+    $page->pressButton('Save');
+    $assert_session->checkboxChecked('layout[allow_custom]');
+  }
+
+}
diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php
index 976a38b3a40fece62b4785b5912cdff73a550a60..e635a84863a543dc986c2ccee305a881e31149de 100644
--- a/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php
+++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTest.php
@@ -9,6 +9,7 @@
  * Tests that the inline block feature works correctly.
  *
  * @group layout_builder
+ * @group #slow
  */
 class InlineBlockTest extends InlineBlockTestBase {
 
diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php
index 53f61b49e32eb99758ffd362e511f02fb0dc5e42..e0df4ad034bc51334e55d92a5ba81f5354b7beef 100644
--- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php
+++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayTest.php
@@ -9,6 +9,7 @@
  * @coversDefaultClass \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay
  *
  * @group layout_builder
+ * @group #slow
  */
 class LayoutBuilderEntityViewDisplayTest extends SectionListTestBase {
 
diff --git a/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php
index f247117b26ee37588450bfe0c0fa17c6959b4963..b4dbf785e17dbaff53894dfa396c43a4abeee3bb 100644
--- a/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php
+++ b/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php
@@ -19,6 +19,7 @@
  * @coversDefaultClass \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage
  *
  * @group layout_builder
+ * @group #slow
  */
 class OverridesSectionStorageTest extends KernelTestBase {
 
diff --git a/core/modules/layout_discovery/tests/src/Functional/GenericTest.php b/core/modules/layout_discovery/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d93515892af6de609a9360e9307966f0f135adb7
--- /dev/null
+++ b/core/modules/layout_discovery/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\layout_discovery\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for layout_discovery.
+ *
+ * @group layout_discovery
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/link/css/link.icon.theme.css b/core/modules/link/css/link.icon.theme.css
index df62539158272dcef8282deb08941907fa0697e3..8924b5a534b2205b16fa939d8986a51fbc626959 100644
--- a/core/modules/link/css/link.icon.theme.css
+++ b/core/modules/link/css/link.icon.theme.css
@@ -5,5 +5,5 @@
  * @preserve
  */
 .field-icon-link {
-  background-image: url("data:image/svg+xml,%3csvg width='21' height='20' fill='none' xmlns='http://www.w3.org/2000/svg'%3e  %3cpath d='M8.197 10.906a4.529 4.529 0 0 0 6.832.49l2.718-2.719a4.53 4.53 0 0 0-6.406-6.405L9.783 3.82' stroke='%2355565B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3e  %3cpath d='M11.821 9.094a4.53 4.53 0 0 0-6.831-.49l-2.718 2.719a4.53 4.53 0 0 0 6.405 6.405l1.55-1.549' stroke='%2355565B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='38' viewBox='0 0 38 38' width='38' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m25.84 2.37c-1.972.311-3.605 1.099-5.162 2.493-1.157 1.035-3.04 2.995-3.221 3.353a1.34 1.34 0 0 0 -.018 1.252c.193.415.819.997 1.219 1.133.798.272 1.01.143 2.997-1.818.891-.88 1.734-1.688 1.873-1.796.678-.523 1.686-.971 2.555-1.135.663-.125 2.055-.089 2.622.068 1.75.484 3.119 1.611 3.911 3.219.493.999.595 1.459.593 2.673-.002 1.102-.093 1.593-.444 2.395-.437.998-.716 1.326-3.528 4.137-1.468 1.467-2.844 2.795-3.058 2.951-1.167.846-2.819 1.293-4.162 1.126a6.606 6.606 0 0 1 -2.194-.674c-.836-.445-1.081-.654-2.231-1.909-.385-.42-.706-.585-1.139-.584-.431.001-.898.215-1.313.604-.579.541-.721 1.135-.423 1.773.157.339.916 1.282 1.378 1.712a9.753 9.753 0 0 0 3.617 2.108c.98.314 1.471.395 2.613.432.91.03 1.195.015 1.842-.096a9.098 9.098 0 0 0 2.767-.918c1.263-.639 1.688-1.007 4.862-4.201 2.382-2.397 2.954-3.006 3.28-3.496 1.732-2.599 2.122-5.727 1.075-8.622-1.126-3.113-3.765-5.388-7.049-6.079-.818-.172-2.484-.224-3.262-.101m-10.64 10.783c-1.249.2-2.102.463-3.071.946-1.308.651-1.648.941-4.727 4.012-1.669 1.666-2.97 3.018-3.178 3.302-.899 1.23-1.444 2.426-1.758 3.857-.168.763-.233 2.364-.128 3.113.583 4.136 3.564 7.335 7.605 8.161 2.581.528 5.344-.096 7.537-1.7.261-.191 1.234-1.1 2.162-2.02 1.865-1.851 2.043-2.083 2.047-2.677.003-.427-.133-.719-.538-1.163-.35-.383-.785-.62-1.212-.661-.581-.056-.836.131-2.744 2.013-1.74 1.715-2.089 2.001-2.908 2.379-.895.414-1.338.502-2.507.499-.947-.002-1.096-.018-1.592-.171-.737-.227-1.185-.431-1.713-.783-1.1-.731-1.953-1.812-2.37-3.006-.489-1.401-.452-3.071.097-4.364.449-1.056.614-1.252 3.451-4.107 1.466-1.475 2.829-2.809 3.03-2.964 1.652-1.284 3.976-1.616 5.891-.842 1.036.419 1.703.931 2.81 2.16.537.595 1.024.749 1.675.527.388-.132.966-.601 1.17-.951.338-.576.258-1.146-.258-1.835-1.526-2.036-3.759-3.341-6.333-3.703-.425-.06-2.108-.075-2.438-.022' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/link/link.module b/core/modules/link/link.module
index 590c01cc91c48e935ba7126200a59ed4a199b6c3..d530b8b3e46308b9b997e1608a5eed6f29ff5b12 100644
--- a/core/modules/link/link.module
+++ b/core/modules/link/link.module
@@ -5,6 +5,7 @@
  * Defines simple link field types.
  */
 
+use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -67,8 +68,10 @@ function template_preprocess_link_formatter_link_separate(&$variables) {
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function link_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'link/drupal.link-icon';
+function link_field_type_category_info_alter(&$definitions) {
+  // The `link` field type belongs in the `general` category, so the libraries
+  // need to be attached using an alter hook.
+  $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'link/drupal.link-icon';
 }
diff --git a/core/modules/link/tests/src/Functional/GenericTest.php b/core/modules/link/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..099d55b064c2e8c752e67211181f9e9b80f67c30
--- /dev/null
+++ b/core/modules/link/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\link\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for link.
+ *
+ * @group link
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index ab64ccf4c8016f8afd2ed3414fc75f3f51aa9a14..3e5f6a183a70753afdd95c8a1ba0961069b68eb8 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -19,6 +19,7 @@
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Installer\InstallerKernel;
 use Drupal\Core\Link;
+use Drupal\Core\Site\Settings;
 use Drupal\Core\Url;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Form\FormStateInterface;
@@ -801,7 +802,7 @@ function locale_translation_get_file_history() {
  */
 function locale_translation_update_file_history($file) {
   $status = \Drupal::database()->merge('locale_file')
-    ->key([
+    ->keys([
       'project' => $file->project,
       'langcode' => $file->langcode,
     ])
@@ -1230,6 +1231,14 @@ function _locale_rebuild_js($langcode = NULL) {
     $translations[$data->context][$data->source] = $data->translation;
   }
 
+  // Include custom string overrides.
+  $custom_strings = Settings::get('locale_custom_strings_' . $language->getId(), []);
+  foreach ($custom_strings as $context => $strings) {
+    foreach ($strings as $source => $translation) {
+      $translations[$context][$source] = $translation;
+    }
+  }
+
   // Construct the JavaScript file, if there are translations.
   $data_hash = NULL;
   $data = $status = '';
diff --git a/core/modules/locale/tests/src/Functional/GenericTest.php b/core/modules/locale/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1105d94d8a8bcc9f45b89fbd5d7d609dab6d7d76
--- /dev/null
+++ b/core/modules/locale/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\locale\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for locale.
+ *
+ * @group locale
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/locale/tests/src/Functional/LocalePluralFormatTest.php b/core/modules/locale/tests/src/Functional/LocalePluralFormatTest.php
index 0daf20edf7f3661d7b9aa96ba70a390ee3b40989..704d349e2b8e9b63373f3cc16db9fe62736e6e67 100644
--- a/core/modules/locale/tests/src/Functional/LocalePluralFormatTest.php
+++ b/core/modules/locale/tests/src/Functional/LocalePluralFormatTest.php
@@ -143,13 +143,13 @@ public function testGetPluralFormat() {
         // expected index as per the logic for translation lookups.
         $expected_plural_index = ($count == 1) ? 0 : $expected_plural_index;
         $expected_plural_string = str_replace('@count', $count, $plural_strings[$langcode][$expected_plural_index]);
-        $this->assertSame($expected_plural_string, \Drupal::translation()->formatPlural($count, '1 hour', '@count hours', [], ['langcode' => $langcode])->render(), 'Plural translation of 1 hours / @count hours for count ' . $count . ' in ' . $langcode . ' is ' . $expected_plural_string);
+        $this->assertSame($expected_plural_string, \Drupal::translation()->formatPlural($count, '@count hour', '@count hours', [], ['langcode' => $langcode])->render(), 'Plural translation of @count hour / @count hours for count ' . $count . ' in ' . $langcode . ' is ' . $expected_plural_string);
         // DO NOT use translation to pass translated strings into
         // PluralTranslatableMarkup::createFromTranslatedString() this way. It
         // is designed to be used with *already* translated text like settings
         // from configuration. We use PHP translation here just because we have
         // the expected result data in that format.
-        $translated_string = \Drupal::translation()->translate('1 hour' . PoItem::DELIMITER . '@count hours', [], ['langcode' => $langcode]);
+        $translated_string = \Drupal::translation()->translate('@count hour' . PoItem::DELIMITER . '@count hours', [], ['langcode' => $langcode]);
         $plural = PluralTranslatableMarkup::createFromTranslatedString($count, $translated_string, [], ['langcode' => $langcode]);
         $this->assertSame($expected_plural_string, $plural->render());
       }
@@ -196,16 +196,16 @@ public function testPluralEditDateFormatter() {
     // langcode here because the language will be English by default and will
     // not save our source string for performance optimization if we do not ask
     // specifically for a language.
-    \Drupal::translation()->formatPlural(1, '1 second', '@count seconds', [], ['langcode' => 'fr'])->render();
+    \Drupal::translation()->formatPlural(1, '@count second', '@count seconds', [], ['langcode' => 'fr'])->render();
     $lid = Database::getConnection()->select('locales_source', 'ls')
       ->fields('ls', ['lid'])
-      ->condition('source', "1 second" . PoItem::DELIMITER . "@count seconds")
+      ->condition('source', "@count second" . PoItem::DELIMITER . "@count seconds")
       ->condition('context', '')
       ->execute()
       ->fetchField();
     // Look up editing page for this plural string and check fields.
     $search = [
-      'string' => '1 second',
+      'string' => '@count second',
       'langcode' => 'fr',
     ];
     $this->drupalGet('admin/config/regional/translate');
@@ -213,7 +213,7 @@ public function testPluralEditDateFormatter() {
 
     // Save complete translations for the string in langcode fr.
     $edit = [
-      "strings[$lid][translations][0]" => '1 seconde updated',
+      "strings[$lid][translations][0]" => '@count seconde updated',
       "strings[$lid][translations][1]" => '@count secondes updated',
     ];
     $this->drupalGet($path);
@@ -251,7 +251,7 @@ public function testPluralEditExport() {
     // Ensure our imported translations exist in the file.
     $this->assertSession()->responseContains("msgid \"Monday\"\nmsgstr \"lundi\"");
     // Check for plural export specifically.
-    $this->assertSession()->responseContains("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure\"\nmsgstr[1] \"@count heures\"");
+    $this->assertSession()->responseContains("msgid \"@count hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure\"\nmsgstr[1] \"@count heures\"");
 
     // Get the Croatian translations.
     $this->drupalGet('admin/config/regional/translate/export');
@@ -261,11 +261,11 @@ public function testPluralEditExport() {
     // Ensure our imported translations exist in the file.
     $this->assertSession()->responseContains("msgid \"Monday\"\nmsgstr \"Ponedjeljak\"");
     // Check for plural export specifically.
-    $this->assertSession()->responseContains("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata\"\nmsgstr[2] \"@count sati\"");
+    $this->assertSession()->responseContains("msgid \"@count hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata\"\nmsgstr[2] \"@count sati\"");
 
     // Check if the source appears on the translation page.
     $this->drupalGet('admin/config/regional/translate');
-    $this->assertSession()->pageTextContains("1 hour");
+    $this->assertSession()->pageTextContains("@count hour");
     $this->assertSession()->pageTextContains("@count hours");
 
     // Look up editing page for this plural string and check fields.
@@ -290,7 +290,7 @@ public function testPluralEditExport() {
     // Edit langcode hr translations and see if that took effect.
     $lid = $connection->select('locales_source', 'ls')
       ->fields('ls', ['lid'])
-      ->condition('source', "1 hour" . PoItem::DELIMITER . "@count hours")
+      ->condition('source', "@count hour" . PoItem::DELIMITER . "@count hours")
       ->condition('context', '')
       ->execute()
       ->fetchField();
@@ -321,16 +321,16 @@ public function testPluralEditExport() {
     // langcode here because the language will be English by default and will
     // not save our source string for performance optimization if we do not ask
     // specifically for a language.
-    \Drupal::translation()->formatPlural(1, '1 day', '@count days', [], ['langcode' => 'fr'])->render();
+    \Drupal::translation()->formatPlural(1, '@count day', '@count days', [], ['langcode' => 'fr'])->render();
     $lid = $connection->select('locales_source', 'ls')
       ->fields('ls', ['lid'])
-      ->condition('source', "1 day" . PoItem::DELIMITER . "@count days")
+      ->condition('source', "@count day" . PoItem::DELIMITER . "@count days")
       ->condition('context', '')
       ->execute()
       ->fetchField();
     // Look up editing page for this plural string and check fields.
     $search = [
-      'string' => '1 day',
+      'string' => '@count day',
       'langcode' => 'fr',
     ];
     $this->drupalGet('admin/config/regional/translate');
@@ -338,7 +338,7 @@ public function testPluralEditExport() {
 
     // Save complete translations for the string in langcode fr.
     $edit = [
-      "strings[$lid][translations][0]" => '1 jour',
+      "strings[$lid][translations][0]" => '@count jour',
       "strings[$lid][translations][1]" => '@count jours',
     ];
     $this->drupalGet($path);
@@ -346,7 +346,7 @@ public function testPluralEditExport() {
 
     // Save complete translations for the string in langcode hr.
     $search = [
-      'string' => '1 day',
+      'string' => '@count day',
       'langcode' => 'hr',
     ];
     $this->drupalGet('admin/config/regional/translate');
@@ -364,15 +364,15 @@ public function testPluralEditExport() {
     $this->drupalGet('admin/config/regional/translate/export');
     $this->submitForm(['langcode' => 'fr'], 'Export');
     // Check for plural export specifically.
-    $this->assertSession()->responseContains("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure edited\"\nmsgstr[1] \"@count heures\"");
-    $this->assertSession()->responseContains("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"1 jour\"\nmsgstr[1] \"@count jours\"");
+    $this->assertSession()->responseContains("msgid \"@count hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count heure edited\"\nmsgstr[1] \"@count heures\"");
+    $this->assertSession()->responseContains("msgid \"@count day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count jour\"\nmsgstr[1] \"@count jours\"");
 
     // Get the Croatian translations.
     $this->drupalGet('admin/config/regional/translate/export');
     $this->submitForm(['langcode' => 'hr'], 'Export');
     // Check for plural export specifically.
-    $this->assertSession()->responseContains("msgid \"1 hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata edited\"\nmsgstr[2] \"@count sati\"");
-    $this->assertSession()->responseContains("msgid \"1 day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count dan\"\nmsgstr[1] \"@count dana\"\nmsgstr[2] \"@count dana\"");
+    $this->assertSession()->responseContains("msgid \"@count hour\"\nmsgid_plural \"@count hours\"\nmsgstr[0] \"@count sat\"\nmsgstr[1] \"@count sata edited\"\nmsgstr[2] \"@count sati\"");
+    $this->assertSession()->responseContains("msgid \"@count day\"\nmsgid_plural \"@count days\"\nmsgstr[0] \"@count dan\"\nmsgstr[1] \"@count dana\"\nmsgstr[2] \"@count dana\"");
   }
 
   /**
@@ -406,12 +406,12 @@ public function getPoFileWithSimplePlural() {
 "Content-Transfer-Encoding: 8bit\\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
 
-msgid "1 hour"
+msgid "@count hour"
 msgid_plural "@count hours"
 msgstr[0] "@count heure"
 msgstr[1] "@count heures"
 
-msgid "1 second"
+msgid "@count second"
 msgid_plural "@count seconds"
 msgstr[0] "@count seconde"
 msgstr[1] "@count secondes"
@@ -434,7 +434,7 @@ public function getPoFileWithComplexPlural() {
 "Content-Transfer-Encoding: 8bit\\n"
 "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
 
-msgid "1 hour"
+msgid "@count hour"
 msgid_plural "@count hours"
 msgstr[0] "@count sat"
 msgstr[1] "@count sata"
diff --git a/core/modules/locale/tests/src/Functional/LocaleTranslationUiTest.php b/core/modules/locale/tests/src/Functional/LocaleTranslationUiTest.php
index 4b8bfaca024fd28105951f79c3f580c9ecc22416..5c4f0c5c4ba630d7c599d346b9da9d09b17425b5 100644
--- a/core/modules/locale/tests/src/Functional/LocaleTranslationUiTest.php
+++ b/core/modules/locale/tests/src/Functional/LocaleTranslationUiTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\locale\Functional;
 
+use Drupal\Core\Site\Settings;
 use Drupal\Core\Url;
 use Drupal\Core\Database\Database;
 use Drupal\language\Entity\ConfigurableLanguage;
@@ -310,6 +311,18 @@ public function testJavaScriptTranslation() {
     $this->assertFileDoesNotExist($js_file);
     _locale_rebuild_js($langcode);
     $this->assertFileExists($js_file);
+
+    // Test if JavaScript translation contains a custom string override.
+    $string_override = $this->randomMachineName();
+    $settings = Settings::getAll();
+    $settings['locale_custom_strings_' . $langcode] = ['' => [$string_override => $string_override]];
+    // Recreate the settings static.
+    new Settings($settings);
+    _locale_rebuild_js($langcode);
+    $locale_javascripts = \Drupal::state()->get('locale.translation.javascript', []);
+    $js_file = 'public://' . $config->get('javascript.directory') . '/' . $langcode . '_' . $locale_javascripts[$langcode] . '.js';
+    $content = file_get_contents($js_file);
+    $this->assertStringContainsString('"' . $string_override . '":"' . $string_override . '"', $content);
   }
 
   /**
diff --git a/core/modules/locale/tests/src/Functional/LocaleUpdateTest.php b/core/modules/locale/tests/src/Functional/LocaleUpdateTest.php
index 1091bb5e923053ba4da839a5a6e568573f284b5b..0f2a78a8d594743098a6ae87aa3d4684b5fb3bcb 100644
--- a/core/modules/locale/tests/src/Functional/LocaleUpdateTest.php
+++ b/core/modules/locale/tests/src/Functional/LocaleUpdateTest.php
@@ -9,6 +9,7 @@
  * Tests for updating the interface translations of projects.
  *
  * @group locale
+ * @group #slow
  */
 class LocaleUpdateTest extends LocaleUpdateBase {
 
diff --git a/core/modules/media/css/media.icon.theme.css b/core/modules/media/css/media.icon.theme.css
index 6bc614d5c8a0043f5db6d738e57a2181329deace..5d4e7d6decd17c1e08e63dc8241e29bc524897fb 100644
--- a/core/modules/media/css/media.icon.theme.css
+++ b/core/modules/media/css/media.icon.theme.css
@@ -6,5 +6,5 @@
  */
 /* cspell:ignore uientity referencemedia */
 .field-icon-field-uientity-referencemedia {
-  background-image: url("data:image/svg+xml,%3csvg width='20' height='20' fill='none' xmlns='http://www.w3.org/2000/svg'%3e  %3cg clip-path='url(%23a)' fill-rule='evenodd' clip-rule='evenodd' fill='%2355565B'%3e    %3cpath d='m19.187 4.864-8.936 2v.16h-.001v8.485a2.401 2.401 0 0 0-1.91-.195c-1.22.376-1.93 1.583-1.587 2.696.343 1.113 1.61 1.71 2.83 1.334 1.084-.334 1.765-1.324 1.664-2.32h.003V9.801l6.93-1.551v6.136a2.401 2.401 0 0 0-1.909-.196c-1.22.376-1.93 1.583-1.587 2.696.343 1.113 1.61 1.71 2.83 1.335 1.083-.334 1.765-1.324 1.663-2.32h.003V8.026l.007-.002v-3.16Z'/%3e    %3cpath d='M13.504.744H.387V12.16h13.117V.744Zm-1.166 1.014H1.553v9.387h.104l2.35-2.281 2.056 1.997L9.25 5.913l2.131 3.309.395-.554.562.788V1.758ZM6.22 4.508a1.943 1.943 0 1 1-3.886 0 1.943 1.943 0 0 1 3.886 0Z'/%3e  %3c/g%3e  %3cdefs%3e    %3cclipPath id='a'%3e      %3cpath fill='%23fff' d='M0 0h20v20H0z'/%3e    %3c/clipPath%3e  %3c/defs%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m1.02 11.52v10.5h17.46v5.203l-.495-.198a4.21 4.21 0 0 0 -2.493-.23c-.951.194-1.625.559-2.324 1.258-.989.988-1.371 2.274-1.049 3.531.171.672.441 1.137.96 1.658.74.741 1.733 1.138 2.846 1.138 2.017-.002 3.781-1.311 4.26-3.162.082-.317.095-.954.095-4.781v-4.416l2.355-.015 2.355-.016.016-3.06.015-3.06 3.855-.858c2.12-.472 3.874-.857 3.899-.855.05.003.063 11.043.013 11.043-.018 0-.161-.066-.319-.146-.669-.339-1.648-.461-2.5-.313a4.415 4.415 0 0 0 -2.489 1.3c-1.49 1.513-1.514 3.723-.058 5.169.774.768 1.682 1.127 2.853 1.129 1.701.003 3.245-.922 3.957-2.369.4-.812.375-.108.409-11.535.029-9.661.024-10.395-.069-10.395-.055 0-2.21.472-4.79 1.05-2.579.578-4.706 1.05-4.726 1.05s-.036-2.052-.036-4.56v-4.56h-24v10.5m21.96-4.715v3.805l-.555.125-2.265.511-1.71.385-.15-.133-.707-.634c-.306-.275-.565-.49-.575-.477s-1.309 2.03-2.887 4.483l-2.87 4.46-.905-.89-1.85-1.818-.944-.929-.256.243-4.081 3.857-.225.211v-17.004h19.98zm-15.69-2.415c-.545.102-1.263.499-1.703.94-.696.699-1.027 1.507-1.027 2.51 0 1.932 1.531 3.45 3.48 3.45a3.453 3.453 0 0 0 3.479-3.472c.001-.606-.09-.989-.378-1.578-.682-1.399-2.248-2.151-3.851-1.85' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/media/media.module b/core/modules/media/media.module
index 9629253158984ddd5550ff16f04eada42d4b0bfb..b2d966a20a40c172538dc9e95fd3cc148c2834fe 100644
--- a/core/modules/media/media.module
+++ b/core/modules/media/media.module
@@ -534,8 +534,10 @@ function media_views_query_substitutions(ViewExecutable $view) {
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function media_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'media/drupal.media-icon';
+function media_field_type_category_info_alter(&$definitions) {
+  // The `media` field type belongs in the `general` category, so the libraries
+  // need to be attached using an alter hook.
+  $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'media/drupal.media-icon';
 }
diff --git a/core/modules/media/src/Entity/MediaType.php b/core/modules/media/src/Entity/MediaType.php
index 68f2508b9e0c92ecff3b9baa879b89345c12b7fd..f4bc587e9d849cba2a777f5d64519d81a1081b11 100644
--- a/core/modules/media/src/Entity/MediaType.php
+++ b/core/modules/media/src/Entity/MediaType.php
@@ -59,6 +59,9 @@
  *     "entity-permissions-form" = "/admin/structure/media/manage/{media_type}/permissions",
  *     "collection" = "/admin/structure/media",
  *   },
+ *   constraints = {
+ *     "ImmutableProperties" = {"id", "source"},
+ *   }
  * )
  */
 class MediaType extends ConfigEntityBundleBase implements MediaTypeInterface, EntityWithPluginCollectionInterface {
diff --git a/core/modules/media/src/Form/EditorMediaDialog.php b/core/modules/media/src/Form/EditorMediaDialog.php
index c76bb9153c0376fc6e5ce240db2632b171b8cf6d..7195f48e3d35b1c469e281393fb947134088f9ee 100644
--- a/core/modules/media/src/Form/EditorMediaDialog.php
+++ b/core/modules/media/src/Form/EditorMediaDialog.php
@@ -26,7 +26,7 @@
  * @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no
  * replacement.
  *
- * @see https://www.drupal.org/project/drupal/issues/3291493
+ * @see https://www.drupal.org/node/3291493
  *
  * @internal
  *   This is an internal part of the media system in Drupal core and may be
@@ -58,7 +58,7 @@ class EditorMediaDialog extends FormBase {
    *   The entity display repository.
    */
   public function __construct(EntityRepositoryInterface $entity_repository, EntityDisplayRepositoryInterface $entity_display_repository) {
-    @trigger_error(__NAMESPACE__ . '\EditorMediaDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493', E_USER_DEPRECATED);
+    @trigger_error(__NAMESPACE__ . '\EditorMediaDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493', E_USER_DEPRECATED);
     $this->entityRepository = $entity_repository;
     $this->entityDisplayRepository = $entity_display_repository;
   }
diff --git a/core/modules/media/src/Form/MediaSettingsForm.php b/core/modules/media/src/Form/MediaSettingsForm.php
index b70aa19d24f64cd99f1c6242c3e00d8c71053a2e..3d8a4e373c570fe66928e9d290e48305d04d5c71 100644
--- a/core/modules/media/src/Form/MediaSettingsForm.php
+++ b/core/modules/media/src/Form/MediaSettingsForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\media\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Form\ConfigFormBase;
@@ -35,13 +36,15 @@ class MediaSettingsForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The config factory service.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\media\IFrameUrlHelper $iframe_url_helper
    *   The iFrame URL helper service.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, IFrameUrlHelper $iframe_url_helper, EntityTypeManagerInterface $entity_type_manager) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, IFrameUrlHelper $iframe_url_helper, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->iFrameUrlHelper = $iframe_url_helper;
     $this->entityTypeManager = $entity_type_manager;
   }
@@ -52,6 +55,7 @@ public function __construct(ConfigFactoryInterface $config_factory, IFrameUrlHel
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('media.oembed.iframe_url_helper'),
       $container->get('entity_type.manager')
     );
diff --git a/core/modules/media/src/MediaConfigUpdater.php b/core/modules/media/src/MediaConfigUpdater.php
index c78bc94fb1560c4cb6e39a838d550a7c27ce2bd9..8544e880070a1f1e92516d88ac51b5f9fb894dc4 100644
--- a/core/modules/media/src/MediaConfigUpdater.php
+++ b/core/modules/media/src/MediaConfigUpdater.php
@@ -63,7 +63,7 @@ public function processOembedEagerLoadField(EntityViewDisplayInterface $view_dis
     $deprecations_triggered = &$this->triggeredDeprecations['3212351'][$view_display->id()];
     if ($this->deprecationsEnabled && $changed && !$deprecations_triggered) {
       $deprecations_triggered = TRUE;
-      @trigger_error(sprintf('The oEmbed loading attribute update for view display "%s" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided configuration should be updated to accommodate the changes described at https://www.drupal.org/node/3275103.', $view_display->id()), E_USER_DEPRECATED);
+      @trigger_error(sprintf('The oEmbed loading attribute update for view display "%s" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3275103', $view_display->id()), E_USER_DEPRECATED);
     }
 
     return $changed;
diff --git a/core/modules/media/src/MediaTypeForm.php b/core/modules/media/src/MediaTypeForm.php
index 64adc89cfb29cb6b1ede9f44b6c0960724107f14..ee815dd21c3744d06347fe164ee76aad9912f5e0 100644
--- a/core/modules/media/src/MediaTypeForm.php
+++ b/core/modules/media/src/MediaTypeForm.php
@@ -97,7 +97,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#title' => $this->t('Name'),
       '#type' => 'textfield',
       '#default_value' => $this->entity->label(),
-      '#description' => $this->t('The human-readable name of this media type.'),
+      '#description' => $this->t('The human-readable name for this media type, displayed on the <em>Media types</em> page.'),
       '#required' => TRUE,
       '#size' => 30,
     ];
@@ -110,14 +110,14 @@ public function form(array $form, FormStateInterface $form_state) {
       '#machine_name' => [
         'exists' => [MediaType::class, 'load'],
       ],
-      '#description' => $this->t('A unique machine-readable name for this media type.'),
+      '#description' => $this->t('Unique machine-readable name: lowercase letters, numbers, and underscores only.'),
     ];
 
     $form['description'] = [
       '#title' => $this->t('Description'),
       '#type' => 'textarea',
       '#default_value' => $this->entity->getDescription(),
-      '#description' => $this->t('Describe this media type. The text will be displayed on the <em>Add new media</em> page.'),
+      '#description' => $this->t('Displays on the <em>Media types</em> page.'),
     ];
 
     $plugins = $this->sourceManager->getDefinitions();
diff --git a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
index 695754b5bf26350e78cd6cffcff3f886d47be6b8..ab590ed41fed2b67cb34d81bebc23ab2472322de 100644
--- a/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
+++ b/core/modules/media/src/Plugin/Field/FieldFormatter/OEmbedFormatter.php
@@ -201,6 +201,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
       }
       else {
         $url = Url::fromRoute('media.oembed_iframe', [], [
+          'absolute' => TRUE,
           'query' => [
             'url' => $value,
             'max_width' => $max_width,
diff --git a/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml b/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
index 7fb69207acf29b6c685dd57a114ec0f6fd7adbfa..8f7f0c8493df848b68dbb53321024455dbd59dc0 100644
--- a/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
+++ b/core/modules/media/tests/modules/media_test_views/config/install/views.view.test_media_bulk_form.yml
@@ -5,7 +5,7 @@ dependencies:
     - media
     - user
 id: test_media_bulk_form
-label: ''
+label: test_media_bulk_form
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/media/tests/src/Functional/FieldFormatter/OembedUpdateTest.php b/core/modules/media/tests/src/Functional/FieldFormatter/OembedUpdateTest.php
index 300a8da24c7c6f933e89e96d4154b1abfbce7f40..83652523a58724506eb4b7bee20dc7c279660a62 100644
--- a/core/modules/media/tests/src/Functional/FieldFormatter/OembedUpdateTest.php
+++ b/core/modules/media/tests/src/Functional/FieldFormatter/OembedUpdateTest.php
@@ -10,6 +10,7 @@
  *
  * @group media
  * @group legacy
+ * @group #slow
  */
 class OembedUpdateTest extends UpdatePathTestBase {
 
@@ -46,7 +47,7 @@ protected function setDatabaseDumpFiles(): void {
    * @legacy
    */
   public function testUpdate(): void {
-    $this->expectDeprecation('The oEmbed loading attribute update for view display "media.remote_video.default" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided configuration should be updated to accommodate the changes described at https://www.drupal.org/node/3275103.');
+    $this->expectDeprecation('The oEmbed loading attribute update for view display "media.remote_video.default" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3275103');
     $data = EntityViewDisplay::load('media.remote_video.default')->toArray();
     $this->assertArrayNotHasKey('loading', $data['content']['field_media_oembed_video']['settings']);
 
diff --git a/core/modules/media/tests/src/Functional/GenericTest.php b/core/modules/media/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..afff4719ab0c2ce882f1622286c1f1604c3d194c
--- /dev/null
+++ b/core/modules/media/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\media\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for media.
+ *
+ * @group media
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
index f8cd65faeb2c7237c6d4a5ffef776fac57f49469..e86300d727410a911d6f77620af8d22324c3cb46 100644
--- a/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
+++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
@@ -13,6 +13,7 @@
  * Ensures that media UI works correctly.
  *
  * @group media
+ * @group #slow
  */
 class MediaUiFunctionalTest extends MediaFunctionalTestBase {
 
@@ -196,7 +197,8 @@ public function testRenderedEntityReferencedMedia() {
     $assert_session = $this->assertSession();
 
     $this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
-    $this->fieldUIAddNewField('/admin/structure/types/manage/page', 'foo_field', 'Foo field', 'field_ui:entity_reference:media', [], [], FALSE);
+    $this->createMediaType('image', ['id' => 'image', 'new_revision' => TRUE]);
+    $this->fieldUIAddNewField('/admin/structure/types/manage/page', 'foo_field', 'Foo field', 'field_ui:entity_reference:media', [], ['settings[handler_settings][target_bundles][image]' => TRUE]);
     $this->drupalGet('/admin/structure/types/manage/page/display');
     $assert_session->fieldValueEquals('fields[field_foo_field][type]', 'entity_reference_entity_view');
   }
@@ -340,13 +342,11 @@ public function testMediaReferenceWidget($cardinality, array $media_type_create_
     // settings form.
     // Using submitForm() to avoid dealing with JavaScript on the previous
     // page in the field creation.
-    $this->fieldUIAddNewField("admin/structure/types/manage/{$content_type->id()}", 'media_reference', "Media (cardinality $cardinality)", 'field_ui:entity_reference:media', [], [], FALSE);
-    $edit = [];
+    $field_edit = [];
     foreach ($media_types as $type) {
-      $edit["settings[handler_settings][target_bundles][$type]"] = TRUE;
+      $field_edit["settings[handler_settings][target_bundles][$type]"] = TRUE;
     }
-    $this->drupalGet("admin/structure/types/manage/{$content_type->id()}/fields/node.{$content_type->id()}.field_media_reference");
-    $this->submitForm($edit, "Save settings");
+    $this->fieldUIAddNewField("admin/structure/types/manage/{$content_type->id()}", 'media_reference', "Media (cardinality $cardinality)", 'field_ui:entity_reference:media', [], $field_edit);
     \Drupal::entityTypeManager()
       ->getStorage('entity_form_display')
       ->load('node.' . $content_type->id() . '.default')
diff --git a/core/modules/media/tests/src/Functional/UrlResolverTest.php b/core/modules/media/tests/src/Functional/UrlResolverTest.php
index b2e1672b9cc80569b26dfe09ef28054b4f0372b2..5b48322878efb03b46bb251bd12836dfb3bf684d 100644
--- a/core/modules/media/tests/src/Functional/UrlResolverTest.php
+++ b/core/modules/media/tests/src/Functional/UrlResolverTest.php
@@ -10,6 +10,7 @@
  * @coversDefaultClass \Drupal\media\OEmbed\UrlResolver
  *
  * @group media
+ * @group #slow
  */
 class UrlResolverTest extends MediaFunctionalTestBase {
 
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiAddTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiAddTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..81f5e4e81eec98127d2cf314ab1b28e3f2846b79
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiAddTest.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+/**
+ * @covers ::media_filter_format_edit_form_validate
+ * @group media
+ * @group #slow
+ */
+class MediaEmbedFilterConfigurationUiAddTest extends MediaEmbedFilterTestBase {
+
+  /**
+   * @covers ::media_form_filter_format_add_form_alter
+   * @dataProvider providerTestValidations
+   */
+  public function testValidationWhenAdding($filter_html_status, $filter_align_status, $filter_caption_status, $filter_html_image_secure_status, $media_embed, $allowed_html, $expected_error_message) {
+    $this->drupalGet('admin/config/content/formats/add');
+
+    // Enable the `filter_html` and `media_embed` filters.
+    $page = $this->getSession()->getPage();
+    $page->fillField('name', 'Another test format');
+    $this->showHiddenFields();
+    $page->findField('format')->setValue('another_media_embed_test');
+    if ($filter_html_status) {
+      $page->checkField('filters[filter_html][status]');
+    }
+    if ($filter_align_status) {
+      $page->checkField('filters[filter_align][status]');
+    }
+    if ($filter_caption_status) {
+      $page->checkField('filters[filter_caption][status]');
+    }
+    if ($filter_html_image_secure_status) {
+      $page->checkField('filters[filter_html_image_secure][status]');
+    }
+    if ($media_embed === TRUE || is_numeric($media_embed)) {
+      $page->checkField('filters[media_embed][status]');
+      // Set a non-default weight.
+      if (is_numeric($media_embed)) {
+        $this->click('.tabledrag-toggle-weight');
+        $page->selectFieldOption('filters[media_embed][weight]', $media_embed);
+      }
+    }
+    if (!empty($allowed_html)) {
+      $page->clickLink('Limit allowed HTML tags and correct faulty HTML');
+      $page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html);
+    }
+    $page->pressButton('Save configuration');
+
+    if ($expected_error_message) {
+      $this->assertSession()->pageTextNotContains('Added text format Another test format.');
+      $this->assertSession()->pageTextContains($expected_error_message);
+    }
+    else {
+      $this->assertSession()->pageTextContains('Added text format Another test format.');
+    }
+  }
+
+}
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiEditTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiEditTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5275c335b635ae4931877f055ed78ae3d7f01f17
--- /dev/null
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiEditTest.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\Tests\media\FunctionalJavascript;
+
+/**
+ * @covers ::media_filter_format_edit_form_validate
+ * @group media
+ * @group #slow
+ */
+class MediaEmbedFilterConfigurationUiEditTest extends MediaEmbedFilterTestBase {
+
+  /**
+   * @covers ::media_form_filter_format_edit_form_alter
+   * @dataProvider providerTestValidations
+   */
+  public function testValidationWhenEditing($filter_html_status, $filter_align_status, $filter_caption_status, $filter_html_image_secure_status, $media_embed, $allowed_html, $expected_error_message) {
+    $this->drupalGet('admin/config/content/formats/manage/media_embed_test');
+
+    // Enable the `filter_html` and `media_embed` filters.
+    $page = $this->getSession()->getPage();
+    if ($filter_html_status) {
+      $page->checkField('filters[filter_html][status]');
+    }
+    if ($filter_align_status) {
+      $page->checkField('filters[filter_align][status]');
+    }
+    if ($filter_caption_status) {
+      $page->checkField('filters[filter_caption][status]');
+    }
+    if ($filter_html_image_secure_status) {
+      $page->checkField('filters[filter_html_image_secure][status]');
+    }
+    if ($media_embed === TRUE || is_numeric($media_embed)) {
+      $page->checkField('filters[media_embed][status]');
+      // Set a non-default weight.
+      if (is_numeric($media_embed)) {
+        $this->click('.tabledrag-toggle-weight');
+        $page->selectFieldOption('filters[media_embed][weight]', $media_embed);
+      }
+    }
+    if (!empty($allowed_html)) {
+      $page->clickLink('Limit allowed HTML tags and correct faulty HTML');
+      $page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html);
+    }
+    $page->pressButton('Save configuration');
+
+    if ($expected_error_message) {
+      $this->assertSession()->pageTextNotContains('The text format Test format has been updated.');
+      $this->assertSession()->pageTextContains($expected_error_message);
+    }
+    else {
+      $this->assertSession()->pageTextContains('The text format Test format has been updated.');
+    }
+  }
+
+}
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterTestBase.php
similarity index 66%
rename from core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
rename to core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterTestBase.php
index 321e2703c691b93e2f93ff6a995e989b50147fe9..9eb26912e58af6dd6097b6797efb18afbb7f1c24 100644
--- a/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterConfigurationUiTest.php
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaEmbedFilterTestBase.php
@@ -5,10 +5,9 @@
 use Drupal\filter\Entity\FilterFormat;
 
 /**
- * @covers ::media_filter_format_edit_form_validate
- * @group media
+ * Base class for media embed filter configuration tests.
  */
-class MediaEmbedFilterConfigurationUiTest extends MediaJavascriptTestBase {
+class MediaEmbedFilterTestBase extends MediaJavascriptTestBase {
 
   /**
    * {@inheritdoc}
@@ -50,97 +49,6 @@ protected function setUp(): void {
     ]));
   }
 
-  /**
-   * @covers ::media_form_filter_format_add_form_alter
-   * @dataProvider providerTestValidations
-   */
-  public function testValidationWhenAdding($filter_html_status, $filter_align_status, $filter_caption_status, $filter_html_image_secure_status, $media_embed, $allowed_html, $expected_error_message) {
-    $this->drupalGet('admin/config/content/formats/add');
-
-    // Enable the `filter_html` and `media_embed` filters.
-    $page = $this->getSession()->getPage();
-    $page->fillField('name', 'Another test format');
-    $this->showHiddenFields();
-    $page->findField('format')->setValue('another_media_embed_test');
-    if ($filter_html_status) {
-      $page->checkField('filters[filter_html][status]');
-    }
-    if ($filter_align_status) {
-      $page->checkField('filters[filter_align][status]');
-    }
-    if ($filter_caption_status) {
-      $page->checkField('filters[filter_caption][status]');
-    }
-    if ($filter_html_image_secure_status) {
-      $page->checkField('filters[filter_html_image_secure][status]');
-    }
-    if ($media_embed === TRUE || is_numeric($media_embed)) {
-      $page->checkField('filters[media_embed][status]');
-      // Set a non-default weight.
-      if (is_numeric($media_embed)) {
-        $this->click('.tabledrag-toggle-weight');
-        $page->selectFieldOption('filters[media_embed][weight]', $media_embed);
-      }
-    }
-    if (!empty($allowed_html)) {
-      $page->clickLink('Limit allowed HTML tags and correct faulty HTML');
-      $page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html);
-    }
-    $page->pressButton('Save configuration');
-
-    if ($expected_error_message) {
-      $this->assertSession()->pageTextNotContains('Added text format Another test format.');
-      $this->assertSession()->pageTextContains($expected_error_message);
-    }
-    else {
-      $this->assertSession()->pageTextContains('Added text format Another test format.');
-    }
-  }
-
-  /**
-   * @covers ::media_form_filter_format_edit_form_alter
-   * @dataProvider providerTestValidations
-   */
-  public function testValidationWhenEditing($filter_html_status, $filter_align_status, $filter_caption_status, $filter_html_image_secure_status, $media_embed, $allowed_html, $expected_error_message) {
-    $this->drupalGet('admin/config/content/formats/manage/media_embed_test');
-
-    // Enable the `filter_html` and `media_embed` filters.
-    $page = $this->getSession()->getPage();
-    if ($filter_html_status) {
-      $page->checkField('filters[filter_html][status]');
-    }
-    if ($filter_align_status) {
-      $page->checkField('filters[filter_align][status]');
-    }
-    if ($filter_caption_status) {
-      $page->checkField('filters[filter_caption][status]');
-    }
-    if ($filter_html_image_secure_status) {
-      $page->checkField('filters[filter_html_image_secure][status]');
-    }
-    if ($media_embed === TRUE || is_numeric($media_embed)) {
-      $page->checkField('filters[media_embed][status]');
-      // Set a non-default weight.
-      if (is_numeric($media_embed)) {
-        $this->click('.tabledrag-toggle-weight');
-        $page->selectFieldOption('filters[media_embed][weight]', $media_embed);
-      }
-    }
-    if (!empty($allowed_html)) {
-      $page->clickLink('Limit allowed HTML tags and correct faulty HTML');
-      $page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html);
-    }
-    $page->pressButton('Save configuration');
-
-    if ($expected_error_message) {
-      $this->assertSession()->pageTextNotContains('The text format Test format has been updated.');
-      $this->assertSession()->pageTextContains($expected_error_message);
-    }
-    else {
-      $this->assertSession()->pageTextContains('The text format Test format has been updated.');
-    }
-  }
-
   /**
    * Data provider for testing validation when adding and editing media embeds.
    */
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php
index 99e5466a70fcd3ce853d2121820006bb5874f9fc..277a68aa2682eb56171db09387ff0f120af9e6b6 100644
--- a/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php
@@ -46,7 +46,7 @@ public function testFieldCreationHelpText() {
     // Choose a boolean field, none of the description containers should be
     // visible.
     $assert_session->elementExists('css', "[name='new_storage_type'][value='boolean']");
-    $page->find('css', "[name='new_storage_type'][value='boolean']")->click();
+    $page->find('css', "[name='new_storage_type'][value='boolean']")->getParent()->click();
     $assert_session->assertWaitOnAjaxRequest();
     $assert_session->pageTextNotContains($help_text);
 
@@ -54,7 +54,8 @@ public function testFieldCreationHelpText() {
     // descriptions are now visible and match the expected text.
     foreach ($field_groups as $field_group) {
       $assert_session->elementExists('css', "[name='new_storage_type'][value='$field_group']");
-      $page->find('css', "[name='new_storage_type'][value='$field_group']")->click();
+      $page->find('css', "[name='new_storage_type'][value='$field_group']")->getParent()->click();
+
       $assert_session->assertWaitOnAjaxRequest();
       $assert_session->pageTextContains($help_text);
     }
diff --git a/core/modules/media/tests/src/Kernel/EditorMediaDialogTest.php b/core/modules/media/tests/src/Kernel/EditorMediaDialogTest.php
index 583aafd477e5c35a89b911d15fc0b03d26b7c217..136307d15dcf9a748ca43fe28ebad4c400db7a91 100644
--- a/core/modules/media/tests/src/Kernel/EditorMediaDialogTest.php
+++ b/core/modules/media/tests/src/Kernel/EditorMediaDialogTest.php
@@ -13,6 +13,7 @@
 /**
  * @coversDefaultClass \Drupal\media\Form\EditorMediaDialog
  * @group media
+ * @group legacy
  */
 class EditorMediaDialogTest extends KernelTestBase {
 
diff --git a/core/modules/media/tests/src/Kernel/MediaDeprecationTest.php b/core/modules/media/tests/src/Kernel/MediaDeprecationTest.php
index 4eb96386e47f3d3089e1121edcf1e6e049e82573..cdb3211c46da43113af46e3cc378833a0ddff72a 100644
--- a/core/modules/media/tests/src/Kernel/MediaDeprecationTest.php
+++ b/core/modules/media/tests/src/Kernel/MediaDeprecationTest.php
@@ -24,7 +24,7 @@ class MediaDeprecationTest extends KernelTestBase {
    * @see EditorMediaDialog
    */
   public function testEditorLinkDialog(): void {
-    $this->expectDeprecation('Drupal\media\Form\EditorMediaDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/project/drupal/issues/3291493');
+    $this->expectDeprecation('Drupal\media\Form\EditorMediaDialog is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3291493');
     new EditorMediaDialog($this->createMock('\Drupal\Core\Entity\EntityRepository'), $this->createMock('\Drupal\Core\Entity\EntityDisplayRepository'));
   }
 
diff --git a/core/modules/media/tests/src/Kernel/MediaTypeValidationTest.php b/core/modules/media/tests/src/Kernel/MediaTypeValidationTest.php
index 8315284280a75d1896f74faae4cca1731a668ada..4f8ba575a529788fac72d3d85b7c5649e0ed566a 100644
--- a/core/modules/media/tests/src/Kernel/MediaTypeValidationTest.php
+++ b/core/modules/media/tests/src/Kernel/MediaTypeValidationTest.php
@@ -27,4 +27,16 @@ protected function setUp(): void {
     $this->entity = $this->createMediaType('test', ['id' => 'test_media']);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function testImmutableProperties(array $valid_values = []): void {
+    // If we don't clear the previous settings here, we will get unrelated
+    // validation errors (in addition to the one we're expecting), because the
+    // settings from the *old* source won't match the config schema for the
+    // settings of the *new* source.
+    $this->entity->set('source_configuration', []);
+    parent::testImmutableProperties($valid_values);
+  }
+
 }
diff --git a/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php b/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
index e4ba1d69f3ec56ef3deb51266964ae5dcce306f7..b24051c71940f07cf3969165011745bbbd173d8b 100644
--- a/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
+++ b/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
@@ -60,6 +60,7 @@ public function checkAccess(MediaLibraryState $state, AccountInterface $account)
       throw new \LogicException("The media library can only be opened by fieldable entities.");
     }
 
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->entityTypeManager->getStorage($entity_type_id);
     $access_handler = $this->entityTypeManager->getAccessControlHandler($entity_type_id);
 
diff --git a/core/modules/media_library/tests/src/Functional/GenericTest.php b/core/modules/media_library/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e42b2a8be0811dccbd72badae11611939aac0f36
--- /dev/null
+++ b/core/modules/media_library/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\media_library\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for media_library.
+ *
+ * @group media_library
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php
index c0f5a9cec7e91334761253bea79bcffbc40ba608..c87def8210b807dde1d17579e363e8cef13f7725 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php
@@ -66,12 +66,14 @@ public function testFieldUiIntegration() {
     $this->drupalLogin($user);
 
     $this->drupalGet('/admin/structure/types/manage/article/fields/add-field');
-    $page->selectFieldOption('new_storage_type', 'field_ui:entity_reference:media');
+    $page->find('css', "[name='new_storage_type'][value='field_ui:entity_reference:media']")->getParent()->click();
     $this->assertNotNull($assert_session->waitForField('label'));
     $page->fillField('label', 'Shatner');
     $this->waitForText('field_shatner');
-    $page->pressButton('Save and continue');
-    $page->pressButton('Save field settings');
+    $page->pressButton('Continue');
+    $this->assertMatchesRegularExpression('/.*article\/add-storage\/node\/field_shatner.*/', $this->getUrl());
+    $page->pressButton('Continue');
+    $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_shatner.*/', $this->getUrl());
     $assert_session->pageTextNotContains('Undefined index: target_bundles');
     $this->waitForFieldExists('Type One')->check();
     $this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_one]"][checked="checked"]');
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
index e9dd15dbeea582e355c219a640269bd1584de6e1..b429213ee05a4425b428d684624308d21bfed484 100644
--- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -89,9 +89,19 @@ public static function create(ContainerInterface $container) {
   public function form(array $form, FormStateInterface $form_state) {
     $form = parent::form($form, $form_state);
 
-    $default = $this->entity->getMenuName() . ':' . $this->entity->getParentId();
+    $parent_id = $this->entity->getParentId() ?: $this->getRequest()->query->get('parent');
+    $default = $this->entity->getMenuName() . ':' . $parent_id;
     $id = $this->entity->isNew() ? '' : $this->entity->getPluginId();
-    $form['menu_parent'] = $this->menuParentSelector->parentSelectElement($default, $id);
+    if ($this->entity->isNew()) {
+      $menu_id = $this->entity->getMenuName();
+      $menu = $this->entityTypeManager->getStorage('menu')->load($menu_id);
+      $form['menu_parent'] = $this->menuParentSelector->parentSelectElement($default, $id, [
+        $menu_id => $menu->label(),
+      ]);
+    }
+    else {
+      $form['menu_parent'] = $this->menuParentSelector->parentSelectElement($default, $id);
+    }
     $form['menu_parent']['#weight'] = 10;
     $form['menu_parent']['#title'] = $this->t('Parent link');
     $form['menu_parent']['#description'] = $this->t('The maximum depth for a link and all its children is fixed. Some menu links may not be available as parents if selecting them would exceed this limit.');
diff --git a/core/modules/menu_link_content/tests/src/Functional/GenericTest.php b/core/modules/menu_link_content/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ec3b0d9a7d5af46a3a51942083578c0a31acf31
--- /dev/null
+++ b/core/modules/menu_link_content/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\menu_link_content\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for menu_link_content.
+ *
+ * @group menu_link_content
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php
index e5d8070439e495695c078dba20f44ce6912cae56..fa4d8cc11a19c077716fb2b3e718e124000dab26 100644
--- a/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php
+++ b/core/modules/menu_link_content/tests/src/Functional/MenuLinkContentFormTest.php
@@ -72,6 +72,9 @@ public function testMenuLinkContentFormLinkToAnyPage() {
     $this->drupalGet('/admin/structure/menu/item/' . $menu_link->id() . '/edit');
     $this->assertSession()->statusCodeEquals(200);
 
+    // Test that other menus are available when editing existing menu link.
+    $this->assertSession()->optionExists('edit-menu-parent', 'main:');
+
     $this->drupalLogin($this->basicUser);
 
     $this->drupalGet('/admin/structure/menu/item/' . $menu_link->id() . '/edit');
@@ -83,6 +86,8 @@ public function testMenuLinkContentFormLinkToAnyPage() {
    */
   public function testMenuLinkContentForm() {
     $this->drupalGet('admin/structure/menu/manage/admin/add');
+    // Test that other menus are not available when creating a new menu link.
+    $this->assertSession()->optionNotExists('edit-menu-parent', 'main:');
     $option = $this->assertSession()->optionExists('edit-menu-parent', 'admin:');
     $this->assertTrue($option->isSelected());
     // Test that the field description is present.
diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php
index c29f73d153e724c810dfea5a8e987bf261ccb051..9d90ac5a607d90f27b5776b722ed9f4feb60fefb 100644
--- a/core/modules/menu_ui/src/MenuForm.php
+++ b/core/modules/menu_ui/src/MenuForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\menu_ui;
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\SortArray;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Form\FormStateInterface;
@@ -434,6 +435,21 @@ protected function buildOverviewTreeForm($tree, $delta) {
           '#default_value' => $link->getParent(),
         ];
         $operations = $link->getOperations();
+        if ($element->depth < $this->menuTree->maxDepth()) {
+          $add_link_url = Url::fromRoute(
+            'entity.menu.add_link_form',
+            ['menu' => $this->entity->id()],
+            ['query' => ['parent' => $link->getPluginId()]]
+          );
+          $operations += [
+            'add-child' => [
+              'title' => $this->t('Add child'),
+              'weight' => 20,
+              'url' => $add_link_url,
+            ],
+          ];
+          uasort($operations, [SortArray::class, 'sortByWeightElement']);
+        }
         foreach ($operations as $key => $operation) {
           if (!isset($operations[$key]['query'])) {
             // Bring the user back to the menu overview.
diff --git a/core/modules/menu_ui/tests/src/Functional/GenericTest.php b/core/modules/menu_ui/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4961f6b6e23afbc801587ffc4fb6c9e0d77802fb
--- /dev/null
+++ b/core/modules/menu_ui/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\menu_ui\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for menu_ui.
+ *
+ * @group menu_ui
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php
index 9cb7d40d03a7f17a2275fe53cba343ae5d8251ba..4caa3c9c1e916b93aa448cfbc795ccf39c14c402 100644
--- a/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php
+++ b/core/modules/menu_ui/tests/src/Functional/MenuUiTest.php
@@ -186,30 +186,40 @@ public function testMenuAdministration() {
     }
 
     // Test alphabetical order without pager.
-    $menus = [];
+    $menu_entities = [];
     for ($i = 1; $i < 6; $i++) {
-      $menus[] = strtolower($this->getRandomGenerator()->name());
-    }
-    sort($menus);
-    foreach ($menus as $menu) {
-      Menu::create(['id' => $menu, 'label' => $menu])->save();
+      $menu = strtolower($this->getRandomGenerator()->name());
+      $menu_entity = Menu::create(['id' => $menu, 'label' => $menu]);
+      $menu_entities[] = $menu_entity;
+      $menu_entity->save();
     }
+    uasort($menu_entities, [Menu::class, 'sort']);
+    $menu_entities = array_values($menu_entities);
     $this->drupalGet('/admin/structure/menu');
     $base_path = parse_url($this->baseUrl, PHP_URL_PATH) ?? '';
     $first_link = $this->assertSession()->elementExists('css', 'tbody tr:nth-of-type(1) a');
     $last_link = $this->assertSession()->elementExists('css', 'tbody tr:nth-of-type(5) a');
-    $this->assertEquals($first_link->getAttribute('href'), sprintf('%s/admin/structure/menu/manage/%s', $base_path, $menus[0]));
-    $this->assertEquals($last_link->getAttribute('href'), sprintf('%s/admin/structure/menu/manage/%s', $base_path, $menus[4]));
+    $this->assertEquals($first_link->getAttribute('href'), sprintf('%s/admin/structure/menu/manage/%s', $base_path, $menu_entities[0]->label()));
+    $this->assertEquals($last_link->getAttribute('href'), sprintf('%s/admin/structure/menu/manage/%s', $base_path, $menu_entities[4]->label()));
+
     // Test alphabetical order with pager.
-    $new_menus = [];
+    $new_menu_entities = [];
     for ($i = 1; $i < 61; $i++) {
-      $new_menus[] = strtolower($this->getRandomGenerator()->name());
-    }
-    foreach ($new_menus as $menu) {
-      Menu::create(['id' => $menu, 'label' => $menu])->save();
+      $new_menu = strtolower($this->getRandomGenerator()->name());
+      $new_menu_entity = Menu::create(['id' => $new_menu, 'label' => $new_menu]);
+      $new_menu_entities[] = $new_menu_entity;
+      $new_menu_entity->save();
     }
-    $menus = array_merge($menus, $new_menus);
-    sort($menus);
+    $menu_entities = array_merge($menu_entities, $new_menu_entities);
+
+    // To accommodate the current non-natural sorting of the pager, we have to
+    // first non-natural sort the array of menu entities, and then do a
+    // natural-sort on the ones that are on page 1.
+    sort($menu_entities);
+    $menu_entities_page_one = array_slice($menu_entities, 50, 64, TRUE);
+    uasort($menu_entities_page_one, [Menu::class, 'sort']);
+    $menu_entities_page_one = array_values($menu_entities_page_one);
+
     $this->drupalGet('/admin/structure/menu', [
       'query' => [
         'page' => 1,
@@ -217,8 +227,8 @@ public function testMenuAdministration() {
     ]);
     $first_link = $this->assertSession()->elementExists('css', 'tbody tr:nth-of-type(1) a');
     $last_link = $this->assertSession()->elementExists('css', 'tbody tr:nth-of-type(15) a');
-    $this->assertEquals($first_link->getAttribute('href'), sprintf('%s/admin/structure/menu/manage/%s', $base_path, $menus[50]));
-    $this->assertEquals($last_link->getAttribute('href'), sprintf('%s/admin/structure/menu/manage/%s', $base_path, $menus[64]));
+    $this->assertEquals($first_link->getAttribute('href'), sprintf('%s/admin/structure/menu/manage/%s', $base_path, $menu_entities_page_one[0]->label()));
+    $this->assertEquals($last_link->getAttribute('href'), sprintf('%s/admin/structure/menu/manage/%s', $base_path, $menu_entities_page_one[14]->label()));
   }
 
   /**
@@ -504,6 +514,10 @@ public function doMenuTests() {
     $this->toggleMenuLink($item1);
     $this->toggleMenuLink($item2);
 
+    // Test the "Add child" link for two siblings.
+    $this->verifyAddChildLink($item5);
+    $this->verifyAddChildLink($item6);
+
     // Move link and verify that descendants are updated.
     $this->moveMenuLink($item2, $item5->getPluginId(), $menu_name);
     // Hierarchy
@@ -787,7 +801,7 @@ public function addInvalidMenuLink() {
    */
   public function checkInvalidParentMenuLinks() {
     $last_link = NULL;
-    $created_links = [];
+    $plugin_ids = [];
 
     // Get the max depth of the tree.
     $menu_link_tree = \Drupal::service('menu.link_tree');
@@ -806,22 +820,32 @@ public function checkInvalidParentMenuLinks() {
         'expanded[value]' => FALSE,
         'weight[0][value]' => '0',
       ];
-      $this->drupalGet("admin/structure/menu/manage/{$this->menu->id()}/add");
+      $this->drupalGet("admin/structure/menu/manage/tools/add");
       $this->submitForm($edit, 'Save');
       $menu_links = \Drupal::entityTypeManager()->getStorage('menu_link_content')->loadByProperties(['title' => $title]);
       $last_link = reset($menu_links);
-      $created_links[] = 'tools:' . $last_link->getPluginId();
+      $plugin_ids[] = $last_link->getPluginId();
     }
 
+    $last_plugin_id = array_pop($plugin_ids);
+
     // The last link cannot be a parent in the new menu link form.
-    $this->drupalGet('admin/structure/menu/manage/admin/add');
-    $value = 'tools:' . $last_link->getPluginId();
+    $this->drupalGet('admin/structure/menu/manage/tools/add');
+    $value = 'tools:' . $last_plugin_id;
     $this->assertSession()->optionNotExists('edit-menu-parent', $value);
 
     // All but the last link can be parents in the new menu link form.
-    array_pop($created_links);
-    foreach ($created_links as $key => $link) {
-      $this->assertSession()->optionExists('edit-menu-parent', $link);
+    foreach ($plugin_ids as $plugin_id) {
+      $this->assertSession()->optionExists('edit-menu-parent', 'tools:' . $plugin_id);
+    }
+
+    // The last link does not have an "Add child" operation.
+    $this->drupalGet('admin/structure/menu/manage/tools');
+    $this->assertSession()->linkByHrefNotExists('parent=' . urlencode($last_plugin_id));
+
+    // All but the last link do have an "Add child" operation.
+    foreach ($plugin_ids as $plugin_id) {
+      $this->assertSession()->linkByHrefExists('parent=' . urlencode($plugin_id));
     }
   }
 
@@ -909,6 +933,25 @@ public function modifyMenuLink(MenuLinkContent $item) {
     $this->assertSession()->pageTextContains($title);
   }
 
+  /**
+   * Verifies that the "Add child" link selects the correct parent item.
+   *
+   * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
+   *   Menu link entity.
+   */
+  public function verifyAddChildLink(MenuLinkContent $item): void {
+    $menu_name = $item->getMenuName();
+
+    $this->drupalGet('admin/structure/menu/manage/' . $menu_name);
+    $links = $this->xpath('//a[normalize-space()=:item_label]/following::a[normalize-space()=:link_label]', [
+      ':item_label' => $item->getTitle(),
+      ':link_label' => 'Add child',
+    ]);
+    $links[0]->click();
+    $option = $this->assertSession()->optionExists('edit-menu-parent', $menu_name . ':' . $item->getPluginId());
+    $this->assertTrue($option->isSelected());
+  }
+
   /**
    * Resets a standard menu link using the UI.
    *
diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php
index 59aada1381ecf73b5fdd8ffc75c3168cdcb71010..e95a1d5cb85d3e42b1b751c47e90a9415b21741c 100644
--- a/core/modules/migrate/src/MigrateExecutable.php
+++ b/core/modules/migrate/src/MigrateExecutable.php
@@ -3,6 +3,7 @@
 namespace Drupal\migrate;
 
 use Drupal\Component\Utility\Bytes;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\Utility\Error;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\migrate\Event\MigrateEvents;
@@ -537,8 +538,8 @@ protected function memoryExceeded() {
           'Memory usage is @usage (@pct% of limit @limit), reclaiming memory.',
           [
             '@pct' => round($pct_memory * 100),
-            '@usage' => $this->formatSize($usage),
-            '@limit' => $this->formatSize($this->memoryLimit),
+            '@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
+            '@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
           ]
         ),
         'warning'
@@ -553,8 +554,8 @@ protected function memoryExceeded() {
             'Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch',
             [
               '@pct' => round($pct_memory * 100),
-              '@usage' => $this->formatSize($usage),
-              '@limit' => $this->formatSize($this->memoryLimit),
+              '@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
+              '@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
             ]
           ),
           'warning'
@@ -567,8 +568,8 @@ protected function memoryExceeded() {
             'Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing',
             [
               '@pct' => round($pct_memory * 100),
-              '@usage' => $this->formatSize($usage),
-              '@limit' => $this->formatSize($this->memoryLimit),
+              '@usage' => ByteSizeMarkup::create($usage, NULL, $this->stringTranslation),
+              '@limit' => ByteSizeMarkup::create($this->memoryLimit, NULL, $this->stringTranslation),
             ]
           ),
           'warning');
@@ -620,8 +621,15 @@ protected function attemptMemoryReclaim() {
    *
    * @return string
    *   A translated string representation of the size.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode)
+   *   instead.
+   *
+   * @see https://www.drupal.org/node/2999981
    */
   protected function formatSize($size) {
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode) instead. See https://www.drupal.org/node/2999981', E_USER_DEPRECATED);
     return format_size($size);
   }
 
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php
index e11f257a34eb9b81146ae6f1ca9e0a8025e3f3b2..f4d565a6014c519ad06183df57532900eb33ffe1 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityContentComplete.php
@@ -59,10 +59,15 @@ protected function getEntity(Row $row, array $old_destination_id_values) {
       : $row->getDestinationProperty($this->getKey('revision'));
     // If we are re-running a migration with set revision IDs and the
     // destination revision ID already exists then do not create a new revision.
-    if (!empty($revision_id) && ($entity = $this->storage->loadRevision($revision_id))) {
-      $entity->setNewRevision(FALSE);
+    $entity = NULL;
+    if (!empty($revision_id)) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+      $storage = $this->storage;
+      if ($entity = $storage->loadRevision($revision_id)) {
+        $entity->setNewRevision(FALSE);
+      }
     }
-    elseif (($entity_id = $row->getDestinationProperty($this->getKey('id'))) && ($entity = $this->storage->load($entity_id))) {
+    if ($entity === NULL && ($entity_id = $row->getDestinationProperty($this->getKey('id'))) && ($entity = $this->storage->load($entity_id))) {
       // We want to create a new entity. Set enforceIsNew() FALSE is  necessary
       // to properly save a new entity while setting the ID. Without it, the
       // system would see that the ID is already set and assume it is an update.
@@ -71,7 +76,7 @@ protected function getEntity(Row $row, array $old_destination_id_values) {
       // not be necessary, it is done for clarity.
       $entity->setNewRevision(TRUE);
     }
-    else {
+    if ($entity === NULL) {
       // Attempt to set the bundle.
       if ($bundle = $this->getBundle($row)) {
         $row->setDestinationProperty($this->getKey('bundle'), $bundle);
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php
index 16d986999ced3733669b2d1f7cdd6f5a41a21c4e..25bf5c7e96ff0d96f50d5c48dbe8fa5e921de703 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityRevision.php
@@ -137,10 +137,15 @@ protected function getEntity(Row $row, array $old_destination_id_values) {
     $revision_id = $old_destination_id_values ?
       reset($old_destination_id_values) :
       $row->getDestinationProperty($this->getKey('revision'));
-    if (!empty($revision_id) && ($entity = $this->storage->loadRevision($revision_id))) {
-      $entity->setNewRevision(FALSE);
+    $entity = NULL;
+    if (!empty($revision_id)) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+      $storage = $this->storage;
+      if ($entity = $storage->loadRevision($revision_id)) {
+        $entity->setNewRevision(FALSE);
+      }
     }
-    else {
+    if ($entity === NULL) {
       $entity_id = $row->getDestinationProperty($this->getKey('id'));
       $entity = $this->storage->load($entity_id);
 
diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
index c0d317ec467d474938c086e74f839ca4c10dd5e8..e7c8240e8906cc7e73b827f12733beddd9cbcd2f 100644
--- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
+++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
@@ -700,11 +700,10 @@ public function saveIdMapping(Row $row, array $destination_id_values, $source_ro
       return;
     }
     $fields['last_imported'] = time();
-    $keys = [$this::SOURCE_IDS_HASH => $this->getSourceIdsHash($source_id_values)];
     // Notify anyone listening of the map row we're about to save.
     $this->eventDispatcher->dispatch(new MigrateMapSaveEvent($this, $fields), MigrateEvents::MAP_SAVE);
     $this->getDatabase()->merge($this->mapTableName())
-      ->key($keys)
+      ->key($this::SOURCE_IDS_HASH, $this->getSourceIdsHash($source_id_values))
       ->fields($fields)
       ->execute();
   }
@@ -1021,7 +1020,7 @@ public function valid() {
    * @see https://www.drupal.org/node/3277306
    */
   protected function getMigrationPluginManager() {
-    @trigger_error('deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. Use $this->migrationPluginManager instead. See https://www.drupal.org/node/3277306', E_USER_DEPRECATED);
+    @trigger_error(__METHOD__ . '() is deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. Use $this->migrationPluginManager instead. See https://www.drupal.org/node/3277306', E_USER_DEPRECATED);
     return $this->migrationPluginManager;
   }
 
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Download.php b/core/modules/migrate/src/Plugin/migrate/process/Download.php
index a2832b66e8a7a97854615e5576db6f6022628622..77a2b9bae5f85f0bf0ee476c5820bbcb652d4cfc 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Download.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Download.php
@@ -31,26 +31,32 @@
  *
  * @code
  * process:
- *   plugin: download
- *   source:
- *     - source_url
- *     - destination_uri
+ *   path_to_file:
+ *     plugin: download
+ *     source:
+ *       - source_url
+ *       - destination_uri
  * @endcode
  *
  * This will download source_url to destination_uri.
  *
  * @code
  * process:
- *   plugin: download
- *   source:
- *     - source_url
- *     - destination_uri
- *   file_exists: rename
+ *   uri:
+ *     plugin: download
+ *     source:
+ *       - source_url
+ *       - destination_uri
+ *     file_exists: rename
+ *   # other fields ...
+ * destination:
+ *   plugin: entity:file
  * @endcode
  *
  * This will download source_url to destination_uri and ensure that the
  * destination URI is unique. If a file with the same name exists at the
  * destination, a numbered suffix like '_0' will be appended to make it unique.
+ * The destination URI is saved in a file entity.
  *
  * @MigrateProcessPlugin(
  *   id = "download"
diff --git a/core/modules/migrate/src/Plugin/migrate/process/SkipOnEmpty.php b/core/modules/migrate/src/Plugin/migrate/process/SkipOnEmpty.php
index 658d897e5124934afd3b72b6984e313eea0b2d47..7e3a86b6fd31504c1b6fdc2438bce82cceba0c13 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/SkipOnEmpty.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/SkipOnEmpty.php
@@ -50,9 +50,28 @@
  *       migration: d6_taxonomy_term
  * @endcode
  * If 'parent' is empty, any further processing of the property is skipped and
- * the next process plugin (migration_lookup) will not be run. Combining
- * skip_on_empty and migration_lookup is a typical process pipeline combination
- * for hierarchical entities where the root entity does not have a parent.
+ * the next process plugin (migration_lookup) will not be run. If the
+ * migration_lookup process is executed it will use 'parent' as the source.
+ * Combining skip_on_empty and migration_lookup is a typical process pipeline
+ * combination for hierarchical entities where the root entity does not have a
+ * parent.
+ *
+ * @code
+ * process:
+ *   parent:
+ *     -
+ *       plugin: skip_on_empty
+ *       method: process
+ *       source: parent
+ *     -
+ *       plugin: migration_lookup
+ *       migration: d6_taxonomy_term
+ *       source: original_term
+ * @endcode
+ * If 'parent' is empty, any further processing of the property is skipped and
+ * the next process plugin (migration_lookup) will not be run. If the
+ * migration_lookup process is executed it will use 'original_term' as the
+ * source.
  *
  * @see \Drupal\migrate\Plugin\MigrateProcessInterface
  *
diff --git a/core/modules/migrate/tests/src/Functional/GenericTest.php b/core/modules/migrate/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0663750c3fda62bf605f6c162f2428bd6f1ce32e
--- /dev/null
+++ b/core/modules/migrate/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\migrate\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for migrate.
+ *
+ * @group migrate
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/migrate/tests/src/Kernel/MigrateMissingDatabaseTest.php b/core/modules/migrate/tests/src/Kernel/MigrateMissingDatabaseTest.php
index 4636db73988e29c963c011fe6658729015394e3c..5049c5437ac4b8138699b61757a671485d36498e 100644
--- a/core/modules/migrate/tests/src/Kernel/MigrateMissingDatabaseTest.php
+++ b/core/modules/migrate/tests/src/Kernel/MigrateMissingDatabaseTest.php
@@ -47,6 +47,10 @@ protected function setUp(): void {
    * - The checkRequirements() method throws a RequirementsException.
    */
   public function testMissingDatabase(): void {
+    if (Database::getConnection()->driver() === 'sqlite') {
+      $this->markTestSkipped('Not compatible with sqlite');
+    }
+
     $migration = $this->migrationPluginManager->createInstance('missing_database');
     $this->assertInstanceOf(MigrationInterface::class, $migration);
     $this->assertInstanceOf(MigrateIdMapInterface::class, $migration->getIdMap());
diff --git a/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php b/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php
index d7d3d0d046c2ab25272dbe82efe40e3ca3860029..105d2c1cb98af5689e15927bb435a41402747852 100644
--- a/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php
+++ b/core/modules/migrate/tests/src/Kernel/SqlBaseTest.php
@@ -132,6 +132,10 @@ public function testConnectionTypes() {
    * Tests the exception when a connection is defined but not available.
    */
   public function testBrokenConnection(): void {
+    if (Database::getConnection()->driver() === 'sqlite') {
+      $this->markTestSkipped('Not compatible with sqlite');
+    }
+
     $sql_base = new TestSqlBase([], $this->migration);
     $target = 'test_state_db_target2';
     $key = 'test_state_migrate_connection2';
diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
index cf7cf0a9b848d4cd293ef6598d7211ce1c69ada8..77fa22e563b07a6edec0b169508a0a1de643a710 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
@@ -1192,7 +1192,7 @@ public function testGetMigrationPluginManagerDeprecation() {
     $container->set('plugin.manager.migration', $migration_plugin_manager);
     \Drupal::setContainer($container);
 
-    $this->expectDeprecation('deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. Use $this->migrationPluginManager instead. See https://www.drupal.org/node/3277306');
+    $this->expectDeprecation('Drupal\migrate\Plugin\migrate\id_map\Sql::getMigrationPluginManager() is deprecated in drupal:9.5.0 and is removed from drupal:11.0.0. Use $this->migrationPluginManager instead. See https://www.drupal.org/node/3277306');
     $id_map = $this->getIdMap();
     $id_map->getMigrationPluginManager();
   }
diff --git a/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php b/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
index ecd56ca9c8448a7854628c04fe1b60fd8b6703f0..0fe7d79a69572c5ab709441203b5bfe5e819bb56 100644
--- a/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
+++ b/core/modules/migrate/tests/src/Unit/TestMigrateExecutable.php
@@ -123,11 +123,4 @@ public function setMemoryThreshold($threshold) {
     $this->memoryThreshold = $threshold;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function formatSize($size) {
-    return $size;
-  }
-
 }
diff --git a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php
index c9724ad8197654187abcad10ca56e3489b8ae21a..56ab6e281f8f1cdd5bc016aee81537bfd0b919e6 100644
--- a/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php
+++ b/core/modules/migrate/tests/src/Unit/destination/EntityRevisionTest.php
@@ -31,7 +31,7 @@ class EntityRevisionTest extends UnitTestCase {
   protected $migration;
 
   /**
-   * @var \Drupal\Core\Entity\EntityStorageInterface
+   * @var \Drupal\Core\Entity\RevisionableStorageInterface
    */
   protected $storage;
 
@@ -60,7 +60,7 @@ protected function setUp(): void {
 
     // Setup mocks to be used when creating a revision destination.
     $this->migration = $this->prophesize(MigrationInterface::class);
-    $this->storage = $this->prophesize('\Drupal\Core\Entity\EntityStorageInterface');
+    $this->storage = $this->prophesize('\Drupal\Core\Entity\RevisionableStorageInterface');
 
     $entity_type = $this->prophesize(EntityTypeInterface::class);
     $entity_type->getSingularLabel()->willReturn('crazy');
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
index 1e75872be6ae5d337c947d9f644643ed7fbfd7e3..2b874381d1cc078fbae39ca235505177324bfad7 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
@@ -20,6 +20,13 @@
  */
 abstract class FieldableEntity extends DrupalSqlBase {
 
+  /**
+   * Cached field and field instance definitions.
+   *
+   * @var array
+   */
+  protected $fieldInfo;
+
   /**
    * Returns all non-deleted field instances attached to a specific entity type.
    *
@@ -38,18 +45,23 @@ abstract class FieldableEntity extends DrupalSqlBase {
    *   The field instances, keyed by field name.
    */
   protected function getFields($entity_type, $bundle = NULL) {
-    $query = $this->select('field_config_instance', 'fci')
-      ->fields('fci')
-      ->condition('fci.entity_type', $entity_type)
-      ->condition('fci.bundle', $bundle ?? $entity_type)
-      ->condition('fci.deleted', 0);
+    $cid = $entity_type . ':' . ($bundle ?? '');
+    if (!isset($this->fieldInfo[$cid])) {
+      $query = $this->select('field_config_instance', 'fci')
+        ->fields('fci')
+        ->condition('fci.entity_type', $entity_type)
+        ->condition('fci.bundle', $bundle ?? $entity_type)
+        ->condition('fci.deleted', 0);
+
+      // Join the 'field_config' table and add the 'translatable' setting to the
+      // query.
+      $query->leftJoin('field_config', 'fc', '[fci].[field_id] = [fc].[id]');
+      $query->addField('fc', 'translatable');
 
-    // Join the 'field_config' table and add the 'translatable' setting to the
-    // query.
-    $query->leftJoin('field_config', 'fc', '[fci].[field_id] = [fc].[id]');
-    $query->addField('fc', 'translatable');
+      $this->fieldInfo[$cid] = $query->execute()->fetchAllAssoc('field_name');
+    }
 
-    return $query->execute()->fetchAllAssoc('field_name');
+    return $this->fieldInfo[$cid];
   }
 
   /**
diff --git a/core/modules/migrate_drupal/tests/src/Functional/GenericTest.php b/core/modules/migrate_drupal/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bedb2d9d3d5e541d026dceed937dcb7ada4d7cba
--- /dev/null
+++ b/core/modules/migrate_drupal/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\migrate_drupal\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for migrate_drupal.
+ *
+ * @group migrate_drupal
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/d6/VariableTranslationTest.php b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/d6/VariableTranslationTest.php
index 38434b9ac96bdb90638aff8061e94c77cf6213bb..803b3af8ea179a804c4639f028a23d1a15ecbfe6 100644
--- a/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/d6/VariableTranslationTest.php
+++ b/core/modules/migrate_drupal/tests/src/Kernel/Plugin/migrate/source/d6/VariableTranslationTest.php
@@ -4,8 +4,6 @@
 
 use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
 
-// cspell:ignore whakamataku génial
-
 /**
  * Tests the variable source plugin.
  *
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/GenericTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..18f4f4ac85f23df6c4d305ae6056ff9075d2e922
--- /dev/null
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\migrate_drupal_ui\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for migrate_drupal_ui.
+ *
+ * @group migrate_drupal_ui
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php
index a5e63e57f751d1262c2ff8539c238e2c74388e5f..cc2119a0b2317793ac71e6ae94b26705f342f998 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6Test.php
@@ -11,6 +11,7 @@
  * The test method is provided by the MigrateUpgradeTestBase class.
  *
  * @group migrate_drupal_ui
+ * @group #slow
  */
 class Upgrade6Test extends MigrateUpgradeExecuteTestBase {
 
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6TestWithContentModeration.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6TestWithContentModeration.php
index ac5cc26df05689f79a01bbb6fbeedf83fd23476b..c5a0b73d3101f25c6a6768983f9c5828ab831f56 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6TestWithContentModeration.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/Upgrade6TestWithContentModeration.php
@@ -9,6 +9,7 @@
  * Tests Drupal 6 upgrade using the migrate UI with Content Moderation.
  *
  * @group migrate_drupal_ui
+ * @group #slow
  */
 class Upgrade6TestWithContentModeration extends Upgrade6Test {
 
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/FilePathTest.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/FilePathTest.php
index c1cbfded2c1318640a1387cce5c9634c696afc26..80a3c9cbf70d5da4652de02c016f343c0b498f4d 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/FilePathTest.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/FilePathTest.php
@@ -7,8 +7,6 @@
 use Drupal\Tests\ExtensionListTestTrait;
 use Drupal\Tests\migrate_drupal_ui\Functional\MigrateUpgradeTestBase;
 
-// cspell:ignore terok
-
 /**
  * Tests the Drupal 7 public and private file migrations.
  *
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php
index 3d84f17d2c09f1b8b3e01fda7ee86ccb914c834e..7c76ccae62463eada905ac4d4b51159f250965f5 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php
@@ -14,6 +14,7 @@
  * The test method is provided by the MigrateUpgradeTestBase class.
  *
  * @group migrate_drupal_ui
+ * @group #slow
  */
 class Upgrade7Test extends MigrateUpgradeExecuteTestBase {
 
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7TestWithContentModeration.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7TestWithContentModeration.php
index 290f4b0656f45f6a1a31a3b1591f5983f9f1aef0..dfb416524885837aa9e261df0cbb49a2054ea532 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7TestWithContentModeration.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7TestWithContentModeration.php
@@ -9,6 +9,7 @@
  * Tests Drupal 7 upgrade using the migrate UI with Content Moderation.
  *
  * @group migrate_drupal_ui
+ * @group #slow
  */
 class Upgrade7TestWithContentModeration extends Upgrade7Test {
 
diff --git a/core/modules/mysql/src/Driver/Database/mysql/Connection.php b/core/modules/mysql/src/Driver/Database/mysql/Connection.php
index e0b33c856f037ba1e3b04b8b990cd62f427b3464..48a42294c070480039a49ec3f830d3fa023a21a1 100644
--- a/core/modules/mysql/src/Driver/Database/mysql/Connection.php
+++ b/core/modules/mysql/src/Driver/Database/mysql/Connection.php
@@ -7,13 +7,11 @@
 use Drupal\Core\Database\DatabaseAccessDeniedException;
 use Drupal\Core\Database\DatabaseConnectionRefusedException;
 use Drupal\Core\Database\DatabaseException;
-use Drupal\Core\Database\DatabaseExceptionWrapper;
 use Drupal\Core\Database\DatabaseNotFoundException;
 use Drupal\Core\Database\Query\Condition;
 use Drupal\Core\Database\StatementWrapperIterator;
 use Drupal\Core\Database\SupportsTemporaryTablesInterface;
-use Drupal\Core\Database\Transaction;
-use Drupal\Core\Database\TransactionNoActiveException;
+use Drupal\Core\Database\Transaction\TransactionManagerInterface;
 
 /**
  * @addtogroup database
@@ -410,109 +408,6 @@ public function nextIdDelete() {
     }
   }
 
-  /**
-   * Overridden to work around issues to MySQL not supporting transactional DDL.
-   */
-  protected function popCommittableTransactions() {
-    // Commit all the committable layers.
-    foreach (array_reverse($this->transactionLayers) as $name => $active) {
-      // Stop once we found an active transaction.
-      if ($active) {
-        break;
-      }
-
-      // If there are no more layers left then we should commit.
-      unset($this->transactionLayers[$name]);
-      if (empty($this->transactionLayers)) {
-        $this->doCommit();
-      }
-      else {
-        // Attempt to release this savepoint in the standard way.
-        try {
-          $this->query('RELEASE SAVEPOINT ' . $name);
-        }
-        catch (DatabaseExceptionWrapper $e) {
-          // However, in MySQL (InnoDB), savepoints are automatically committed
-          // when tables are altered or created (DDL transactions are not
-          // supported). This can cause exceptions due to trying to release
-          // savepoints which no longer exist.
-          //
-          // To avoid exceptions when no actual error has occurred, we silently
-          // succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
-          if ($e->getPrevious()->errorInfo[1] == '1305') {
-            // If one SAVEPOINT was released automatically, then all were.
-            // Therefore, clean the transaction stack.
-            $this->transactionLayers = [];
-            // We also have to explain to PDO that the transaction stack has
-            // been cleaned-up.
-            $this->doCommit();
-          }
-          else {
-            throw $e;
-          }
-        }
-      }
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function rollBack($savepoint_name = 'drupal_transaction') {
-    // MySQL will automatically commit transactions when tables are altered or
-    // created (DDL transactions are not supported). Prevent triggering an
-    // exception to ensure that the error that has caused the rollback is
-    // properly reported.
-    if (!$this->connection->inTransaction()) {
-      // On PHP 7 $this->connection->inTransaction() will return TRUE and
-      // $this->connection->rollback() does not throw an exception; the
-      // following code is unreachable.
-
-      // If \Drupal\Core\Database\Connection::rollBack() would throw an
-      // exception then continue to throw an exception.
-      if (!$this->inTransaction()) {
-        throw new TransactionNoActiveException();
-      }
-      // A previous rollback to an earlier savepoint may mean that the savepoint
-      // in question has already been accidentally committed.
-      if (!isset($this->transactionLayers[$savepoint_name])) {
-        throw new TransactionNoActiveException();
-      }
-
-      trigger_error('Rollback attempted when there is no active transaction. This can cause data integrity issues.', E_USER_WARNING);
-      return;
-    }
-    return parent::rollBack($savepoint_name);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function doCommit() {
-    // MySQL will automatically commit transactions when tables are altered or
-    // created (DDL transactions are not supported). Prevent triggering an
-    // exception in this case as all statements have been committed.
-    if ($this->connection->inTransaction()) {
-      // On PHP 7 $this->connection->inTransaction() will return TRUE and
-      // $this->connection->commit() does not throw an exception.
-      $success = parent::doCommit();
-    }
-    else {
-      // Process the post-root (non-nested) transaction commit callbacks. The
-      // following code is copied from
-      // \Drupal\Core\Database\Connection::doCommit()
-      $success = TRUE;
-      if (!empty($this->rootTransactionEndCallbacks)) {
-        $callbacks = $this->rootTransactionEndCallbacks;
-        $this->rootTransactionEndCallbacks = [];
-        foreach ($callbacks as $callback) {
-          call_user_func($callback, $success);
-        }
-      }
-    }
-    return $success;
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -586,11 +481,18 @@ public function condition($conjunction) {
     return new Condition($conjunction);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function driverTransactionManager(): TransactionManagerInterface {
+    return new TransactionManager($this);
+  }
+
   /**
    * {@inheritdoc}
    */
   public function startTransaction($name = '') {
-    return new Transaction($this, $name);
+    return $this->transactionManager()->push($name);
   }
 
 }
diff --git a/core/modules/mysql/src/Driver/Database/mysql/TransactionManager.php b/core/modules/mysql/src/Driver/Database/mysql/TransactionManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..3b7ce03f5f77b44fee745ed20c3437fc742c55f1
--- /dev/null
+++ b/core/modules/mysql/src/Driver/Database/mysql/TransactionManager.php
@@ -0,0 +1,99 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\mysql\Driver\Database\mysql;
+
+use Drupal\Core\Database\Transaction\ClientConnectionTransactionState;
+use Drupal\Core\Database\Transaction\TransactionManagerBase;
+
+/**
+ * MySql implementation of TransactionManagerInterface.
+ *
+ * MySQL will automatically commit transactions when tables are altered or
+ * created (DDL transactions are not supported). However, pdo_mysql tracks
+ * whether a client connection is still active and we can prevent triggering
+ * exceptions.
+ */
+class TransactionManager extends TransactionManagerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function beginClientTransaction(): bool {
+    return $this->connection->getClientConnection()->beginTransaction();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function processRootCommit(): void {
+    if (!$this->connection->getClientConnection()->inTransaction()) {
+      $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided);
+      $this->processPostTransactionCallbacks();
+      return;
+    }
+    parent::processRootCommit();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function rollbackClientSavepoint(string $name): bool {
+    if (!$this->connection->getClientConnection()->inTransaction()) {
+      $this->resetStack();
+      $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided);
+      $this->processPostTransactionCallbacks();
+      return TRUE;
+    }
+    return parent::rollbackClientSavepoint($name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function releaseClientSavepoint(string $name): bool {
+    if (!$this->connection->getClientConnection()->inTransaction()) {
+      $this->resetStack();
+      $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided);
+      $this->processPostTransactionCallbacks();
+      return TRUE;
+    }
+    return parent::releaseClientSavepoint($name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function commitClientTransaction(): bool {
+    if (!$this->connection->getClientConnection()->inTransaction()) {
+      $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided);
+      $this->processPostTransactionCallbacks();
+      return TRUE;
+    }
+    $clientCommit = $this->connection->getClientConnection()->commit();
+    $this->setConnectionTransactionState($clientCommit ?
+      ClientConnectionTransactionState::Committed :
+      ClientConnectionTransactionState::CommitFailed
+    );
+    return $clientCommit;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function rollbackClientTransaction(): bool {
+    if (!$this->connection->getClientConnection()->inTransaction()) {
+      $this->setConnectionTransactionState(ClientConnectionTransactionState::Voided);
+      $this->processPostTransactionCallbacks();
+      trigger_error('Rollback attempted when there is no active transaction. This can cause data integrity issues.', E_USER_WARNING);
+    }
+    $clientRollback = $this->connection->getClientConnection()->rollBack();
+    $this->setConnectionTransactionState($clientRollback ?
+      ClientConnectionTransactionState::RolledBack :
+      ClientConnectionTransactionState::RollbackFailed
+    );
+    return $clientRollback;
+  }
+
+}
diff --git a/core/modules/mysql/tests/src/Functional/GenericTest.php b/core/modules/mysql/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b086fd5dc9a5c64d5aa80e8804c9236d42be2dc
--- /dev/null
+++ b/core/modules/mysql/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\mysql\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for mysql.
+ *
+ * @group mysql
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index f47dbb754094f566b00b9891709cb1c2465e9c8b..0e2fa13b4fb83f236089aa1542ee2bd99edf18fd 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1185,10 +1185,11 @@ function _node_access_rebuild_batch_operation(&$context) {
     ->execute();
   $node_storage->resetCache($nids);
   $nodes = Node::loadMultiple($nids);
-  foreach ($nodes as $nid => $node) {
+  foreach ($nids as $nid) {
     // To preserve database integrity, only write grants if the node
     // loads successfully.
-    if (!empty($node)) {
+    if (!empty($nodes[$nid])) {
+      $node = $nodes[$nid];
       /** @var \Drupal\node\NodeAccessControlHandlerInterface $access_control_handler */
       $access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node');
       $grants = $access_control_handler->acquireGrants($node);
diff --git a/core/modules/node/src/Form/NodeRevisionDeleteForm.php b/core/modules/node/src/Form/NodeRevisionDeleteForm.php
index a413c8fe11fa86031667f0090c5c9686adbcf33a..1d6af272b731b97fe29f5cb7b05fd60da856c47f 100644
--- a/core/modules/node/src/Form/NodeRevisionDeleteForm.php
+++ b/core/modules/node/src/Form/NodeRevisionDeleteForm.php
@@ -142,7 +142,9 @@ public function buildForm(array $form, FormStateInterface $form_state, NodeInter
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    $this->nodeStorage->deleteRevision($this->revision->getRevisionId());
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = $this->nodeStorage;
+    $storage->deleteRevision($this->revision->getRevisionId());
 
     $this->logger('content')->info('@type: deleted %title revision %revision.', ['@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]);
     $node_type = $this->nodeTypeStorage->load($this->revision->bundle())->label();
diff --git a/core/modules/node/src/NodeInterface.php b/core/modules/node/src/NodeInterface.php
index d762e3a3a1af6f19f2cc6c11a80fcd3e55776e72..89d9d114c6a279d18dc69dbc363c62fff47737b3 100644
--- a/core/modules/node/src/NodeInterface.php
+++ b/core/modules/node/src/NodeInterface.php
@@ -54,8 +54,9 @@ public function getType();
   /**
    * Gets the node title.
    *
-   * @return string
-   *   Title of the node.
+   * @return string|null
+   *   Title of the node, or NULL if the node doesn't yet have a title (for
+   *   example, if a new node is being previewed).
    */
   public function getTitle();
 
diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/NodeTypeForm.php
index 65bb5dd6d16ed19ebb2b386c3a7bea228891f96c..00f829af35ba891c72d4d9b0b4582cb0d18346d9 100644
--- a/core/modules/node/src/NodeTypeForm.php
+++ b/core/modules/node/src/NodeTypeForm.php
@@ -69,7 +69,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#title' => $this->t('Name'),
       '#type' => 'textfield',
       '#default_value' => $type->label(),
-      '#description' => $this->t('The human-readable name of this content type. This text will be displayed as part of the list on the <em>Add content</em> page. This name must be unique.'),
+      '#description' => $this->t('The human-readable name for this content type, displayed on the <em>Content types</em> page.'),
       '#required' => TRUE,
       '#size' => 30,
     ];
@@ -83,7 +83,7 @@ public function form(array $form, FormStateInterface $form_state) {
         'exists' => ['Drupal\node\Entity\NodeType', 'load'],
         'source' => ['name'],
       ],
-      '#description' => $this->t('A unique machine-readable name for this content type. It must only contain lowercase letters, numbers, and underscores. This name will be used for constructing the URL of the %node-add page.', [
+      '#description' => $this->t('Unique machine-readable name: lowercase letters, numbers, and underscores only.', [
         '%node-add' => $this->t('Add content'),
       ]),
     ];
@@ -92,7 +92,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#title' => $this->t('Description'),
       '#type' => 'textarea',
       '#default_value' => $type->getDescription(),
-      '#description' => $this->t('This text will be displayed on the <em>Add new content</em> page.'),
+      '#description' => $this->t('Displays on the <em>Content types</em> page.'),
     ];
 
     $form['additional_settings'] = [
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
index 78536b8314509ddbb14e45128dd8b8125641862f..d7a3fa080fed21b4077504d9f3eb15e5556fe36f 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_bulk_form.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_node_bulk_form
-label: ''
+label: test_node_bulk_form
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_nid.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_nid.yml
index 7708e9892a012f0c61897d10c8e48d63d0c242cf..ac645e0e0f585685803d1cfe1c48b6edb737cb9a 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_nid.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_nid.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_node_revision_nid
-label: null
+label: test_node_revision_nid
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_timestamp.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_timestamp.yml
index 9497de6b17cdadf4c240e03be38f733a13fd1f7a..8d4f0357f59df0be7357eaf651e3827ec54156ca 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_timestamp.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_timestamp.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_node_revision_timestamp
-label: null
+label: test_node_revision_timestamp
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_vid.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_vid.yml
index 96977fb4d10c2612c6853923a3d4443d86574bd8..fc9fb4e13d96eb227f181626e42bbeeb4cdc3eef 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_vid.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_node_revision_vid.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_node_revision_vid
-label: null
+label: test_node_revision_vid
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/node/tests/src/Functional/GenericTest.php b/core/modules/node/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..100f1d26ee4031bae903163a973e2bf8df9c17ef
--- /dev/null
+++ b/core/modules/node/tests/src/Functional/GenericTest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Tests\node\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for node.
+ *
+ * @group node
+ */
+class GenericTest extends GenericModuleTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    // Ensure the generic test base is working as expected.
+    $this->assertSame('node', $this->getModule());
+    parent::setUp();
+  }
+
+}
diff --git a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
index e808975efdb24c242bd08c1691a51d434570645b..202fcbceae2d341cd52cc0e72f2127612e1e9f3b 100644
--- a/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
+++ b/core/modules/node/tests/src/Functional/NodeTranslationUITest.php
@@ -13,6 +13,7 @@
  * Tests the Node Translation UI.
  *
  * @group node
+ * @group #slow
  */
 class NodeTranslationUITest extends ContentTranslationUITestBase {
 
@@ -560,7 +561,7 @@ public function testDetailsTitleIsNotEscaped() {
     // details form element.
     $edit = ['cardinality_number' => 2];
     $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_image/storage');
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
 
     // Make the image field non-translatable.
     $edit = ['settings[node][article][fields][field_image]' => FALSE];
diff --git a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php
index bbab173121b87ec6b288a2efacaa98f9340d979b..4bfbbe48de5dea1c0ee4fa12d02135eb42b32805 100644
--- a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php
+++ b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php
@@ -172,8 +172,8 @@ public function testNodeTypeTitleLabelTranslation() {
       'new_storage_type' => 'email',
       'label' => 'Email',
       'field_name' => 'email',
-    ], 'Save and continue');
-    $this->submitForm([], 'Save field settings');
+    ], 'Continue');
+    $this->submitForm([], 'Continue');
     $this->submitForm([], 'Save settings');
 
     $type = $this->randomMachineName(16);
diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeCompleteTest.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeCompleteTest.php
index dc2a349ba462de44a38ca29370373d5bd0820c8f..cf664c9d0e055e8bafbcda3e7bb96e9efebd7fc1 100644
--- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeCompleteTest.php
+++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeCompleteTest.php
@@ -28,7 +28,7 @@ class MigrateNodeCompleteTest extends MigrateNodeTestBase {
   /**
    * The entity storage for node.
    *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
+   * @var \Drupal\Core\Entity\RevisionableStorageInterface
    */
   protected $nodeStorage;
 
diff --git a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTest.php b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTest.php
index ed3bc55add437c4a04fc79e7d707ab7d46423c57..f200e3059928cac1cca3da0d1d4da30f968a8adf 100644
--- a/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTest.php
+++ b/core/modules/node/tests/src/Kernel/Migrate/d6/MigrateNodeRevisionTest.php
@@ -14,7 +14,7 @@ class MigrateNodeRevisionTest extends MigrateNodeTestBase {
   /**
    * The entity storage for node.
    *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
+   * @var \Drupal\Core\Entity\RevisionableStorageInterface
    */
   protected $nodeStorage;
 
diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
index 3fb3d6ded683ce9a78c5e2e013b046f3ed993148..6c967d33f95fb76c1e6dca0cd3acdbe8ee331a1c 100644
--- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
+++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeCompleteTest.php
@@ -43,7 +43,7 @@ class MigrateNodeCompleteTest extends MigrateDrupal7TestBase {
   /**
    * The entity storage for node.
    *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
+   * @var \Drupal\Core\Entity\RevisionableStorageInterface
    */
   protected $nodeStorage;
 
diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeRevisionTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeRevisionTest.php
index 04656656c2ac67bf6c0670629306c5a14b873f3e..b45fed752845eeda16112c7f2277f6dd8d1b0073 100644
--- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeRevisionTest.php
+++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeRevisionTest.php
@@ -18,7 +18,7 @@ class MigrateNodeRevisionTest extends MigrateDrupal7TestBase {
   /**
    * The entity storage for node.
    *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
+   * @var \Drupal\Core\Entity\RevisionableStorageInterface
    */
   protected $nodeStorage;
 
diff --git a/core/modules/options/css/options.icon.theme.css b/core/modules/options/css/options.icon.theme.css
index 5c66700be357f3b48f1285fe45591dfbd9663ca4..0fc18882d3892f9b19b84a9adf5a6db393872bcb 100644
--- a/core/modules/options/css/options.icon.theme.css
+++ b/core/modules/options/css/options.icon.theme.css
@@ -5,5 +5,5 @@
  * @preserve
  */
 .field-icon-selection_list {
-  background-image: url("data:image/svg+xml,%3csvg width='18' height='15' viewBox='0 0 18 15' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5 6.5H18V8.5H5V6.5Z' fill='%2355565B'/%3e%3cpath d='M3 6H0V9H3V6Z' fill='%2355565B'/%3e%3cpath d='M18 12.5H5V14.5H18V12.5Z' fill='%2355565B'/%3e%3cpath d='M3 12H0V15H3V12Z' fill='%2355565B'/%3e%3cpath d='M18 0.5H5V2.5H18V0.5Z' fill='%2355565B'/%3e%3cpath d='M3 0H0V3H3V0Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m4.98 9.51v2.49h5.04v-4.98h-5.04zm7.02 0v1.47h19.98v-2.94h-19.98zm-7.02 9v2.49h5.04v-4.98h-5.04zm7.02-.03v1.5h19.98v-3h-19.98zm-7.02 9.03v2.49h5.04v-4.98h-5.04zm7.02 0v1.47h19.98v-2.94h-19.98z' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/options/options.field_type_categories.yml b/core/modules/options/options.field_type_categories.yml
index 9c23ff067057556ecb1d3fb497dd696a470e0955..339cc386c8230d412cbb0ae60c9fb5c59938c94e 100644
--- a/core/modules/options/options.field_type_categories.yml
+++ b/core/modules/options/options.field_type_categories.yml
@@ -2,3 +2,5 @@ selection_list:
   label: 'Selection list'
   description: 'Field to select from predefined options.'
   weight: -15
+  libraries:
+    - options/drupal.options-icon
diff --git a/core/modules/options/options.module b/core/modules/options/options.module
index 2feed0daf8060235a5291991cde9f088ad92b4cf..608f4bcc6586553976e12ab1fdbf416a931f0315 100644
--- a/core/modules/options/options.module
+++ b/core/modules/options/options.module
@@ -155,10 +155,3 @@ function options_form_field_storage_config_edit_form_alter(&$form, FormStateInte
   $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
   $table['#attributes']['class'][] = 'allowed-values-table';
 }
-
-/**
- * Implements hook_preprocess_form_element__new_storage_type().
- */
-function options_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'options/drupal.options-icon';
-}
diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php
index 9e5f704a2f708156b6f1f3a9ecc66dfceebbc96f..3bdf31578a8d71a9902a33afce6e5000ba71548c 100644
--- a/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php
+++ b/core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php
@@ -185,6 +185,10 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
         '#default_value' => 0,
         '#attributes' => ['class' => ['weight']],
       ];
+      // Disable the remove button if there is only one row in the table.
+      if ($max === 0) {
+        $element['allowed_values']['table'][0]['delete']['#attributes']['disabled'] = 'disabled';
+      }
       if ($delta < count($allowed_values)) {
         $query = \Drupal::entityQuery($entity_type_id)
           ->accessCheck(FALSE)
@@ -261,6 +265,10 @@ public static function addMoreAjax(array $form, FormStateInterface $form_state)
     $delta = $element['table']['#max_delta'];
     $element['table'][$delta]['item']['#prefix'] = '<div class="ajax-new-content" data-drupal-selector="field-list-add-more-focus-target">' . ($element['table'][$delta]['item']['#prefix'] ?? '');
     $element['table'][$delta]['item']['#suffix'] = ($element['table'][$delta]['item']['#suffix'] ?? '') . '</div>';
+    // Enable the remove button for the first row if there are more rows.
+    if ($delta > 0 && isset($element['table'][0]['delete']['#attributes']['disabled']) && !isset($element['table'][0]['item']['key']['#attributes']['disabled'])) {
+      unset($element['table'][0]['delete']['#attributes']['disabled']);
+    }
 
     $response = new AjaxResponse();
     $response->addCommand(new InsertCommand(NULL, $element));
@@ -384,7 +392,7 @@ protected static function extractAllowedValues($list, $has_data) {
     $values = [];
 
     if (is_string($list)) {
-      trigger_error('Passing a string to ' . __METHOD__ . '() is deprecated in drupal:10.2.0 and will be removed from drupal:11.0.0. Use an array instead.', E_USER_DEPRECATED);
+      trigger_error('Passing a string to ' . __METHOD__ . '() is deprecated in drupal:10.2.0 and will cause an error from drupal:11.0.0. Use an array instead. See https://www.drupal.org/node/3376368', E_USER_DEPRECATED);
       $list = explode("\n", $list);
       $list = array_map('trim', $list);
       $list = array_filter($list, 'strlen');
diff --git a/core/modules/options/tests/src/Functional/GenericTest.php b/core/modules/options/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..61f5954d8a11a2522663b12c4d6c70a4e2000c53
--- /dev/null
+++ b/core/modules/options/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\options\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for options.
+ *
+ * @group options
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
index 93bbca1d09405fee3f28742ab2c299de4503e593..c89e3e8ea3170ee2d5bcdb2a7ad25c3d3b0360ab 100644
--- a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
+++ b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
@@ -373,7 +373,7 @@ public function assertAllowedValuesInput(array $input, $result, string $message)
     $add_button->click();
     $add_button->click();
 
-    $this->submitForm($input, 'Save field settings');
+    $this->submitForm($input, 'Save');
     // Verify that the page does not have double escaped HTML tags.
     $this->assertSession()->responseNotContains('&amp;lt;');
 
@@ -406,8 +406,7 @@ public function testNodeDisplay() {
     $this->drupalGet($this->adminPath);
     $page = $this->getSession()->getPage();
     $page->findButton('Add another item')->click();
-    $this->submitForm($edit, 'Save field settings');
-    $this->assertSession()->pageTextContains('Updated field ' . $this->fieldName . ' field settings.');
+    $this->submitForm($edit, 'Save');
 
     // Select a default value.
     $edit = [
@@ -454,9 +453,22 @@ public function testRequiredPropertyForAllowedValuesList() {
       $this->createOptionsField($field_type);
       $page = $this->getSession()->getPage();
 
-      // Try to proceed without entering any value.
       $this->drupalGet($this->adminPath);
-      $page->findButton('Save field settings')->click();
+      // Assert that the delete button for a single row is disabled.
+      $this->assertCount(1, $page->findAll('css', '#allowed-values-order tr.draggable'));
+      $delete_button_0 = $page->findById('remove_row_button__0');
+      $this->assertTrue($delete_button_0->hasAttribute('disabled'), 'Button is disabled');
+      $page->findButton('Add another item')->click();
+      // Assert that the delete button for the first row is enabled if there are
+      // more that one rows.
+      $this->assertCount(2, $page->findAll('css', '#allowed-values-order tr.draggable'));
+      $this->assertFalse($delete_button_0->hasAttribute('disabled'), 'Button is enabled');
+      // Delete a row.
+      $delete_button_0->click();
+      // Assert that the button is disabled again.
+      $this->assertTrue($delete_button_0->hasAttribute('disabled'), 'Button is disabled');
+      // Try to proceed without entering any value.
+      $page->findButton('Save')->click();
 
       if ($field_type == 'list_string') {
         // Asserting only name field as there is no value field for list_string.
diff --git a/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php b/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php
index dfd5ad0645e639b344466547b41dbca05fda950d..2610530d2a4b7f8cc530571260d84cfd448ce24d 100644
--- a/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php
+++ b/core/modules/options/tests/src/Functional/OptionsFloatFieldImportTest.php
@@ -78,7 +78,7 @@ public function testImport() {
       'settings[allowed_values][table][1][item][label]' => 'One',
     ];
     $this->drupalGet($admin_path);
-    $this->submitForm($edit, 'Save field settings');
+    $this->submitForm($edit, 'Save');
     $field_storage = FieldStorageConfig::loadByName('node', $field_name);
     $this->assertSame($array = ['0' => 'Zero', '1' => 'One'], $field_storage->getSetting('allowed_values'));
 
diff --git a/core/modules/options/tests/src/Functional/OptionsWidgetsTest.php b/core/modules/options/tests/src/Functional/OptionsWidgetsTest.php
index 660bd11f60d38983ecdb8ab25ab213ab1eee8d58..769f2f260bf5e76c272aa9b0137d2f3c540f7e76 100644
--- a/core/modules/options/tests/src/Functional/OptionsWidgetsTest.php
+++ b/core/modules/options/tests/src/Functional/OptionsWidgetsTest.php
@@ -11,6 +11,7 @@
  * Tests the Options widgets.
  *
  * @group options
+ * @group #slow
  */
 class OptionsWidgetsTest extends FieldTestBase {
 
diff --git a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php
index 56d39c0b4dd83be2969dcf7486c48b99010216a0..593fd85ef041dd3ce49635776df84995333f3748 100644
--- a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php
+++ b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php
@@ -162,7 +162,7 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti
       $this->assertHasFocusByAttribute('name', $key_element_name);
       $this->assertAllowValuesRowCount($expected_rows);
     }
-    $page->pressButton('Save field settings');
+    $page->pressButton('Save');
 
     // Test the order of the option list on node form.
     $this->drupalGet($this->nodeFormPath);
@@ -177,7 +177,7 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti
     // Change the order the items appear.
     $drag_handle->dragTo($target);
     $this->assertOrder(['Second', 'Third', 'First', ''], $is_string_option);
-    $page->pressButton('Save field settings');
+    $page->pressButton('Save');
 
     $this->drupalGet($this->nodeFormPath);
     $this->assertNodeFormOrder(['- None -', 'Second', 'Third', 'First']);
@@ -191,7 +191,7 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti
     $page->pressButton('remove_row_button__1');
     $this->assertSession()->assertWaitOnAjaxRequest();
     $this->assertOrder(['Second', 'First', ''], $is_string_option);
-    $page->pressButton('Save field settings');
+    $page->pressButton('Save');
 
     $this->drupalGet($this->nodeFormPath);
     $this->assertNodeFormOrder(['- None -', 'Second', 'First']);
diff --git a/core/modules/page_cache/tests/src/Functional/GenericTest.php b/core/modules/page_cache/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..20bc76fd0586a12b72bfbe1ca90a67e2189a6058
--- /dev/null
+++ b/core/modules/page_cache/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\page_cache\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for page_cache.
+ *
+ * @group page_cache
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/page_cache/tests/src/Functional/PageCacheTest.php b/core/modules/page_cache/tests/src/Functional/PageCacheTest.php
index a1d9a9ac53b982db20763ab5d4214b2535c8a3f0..9824e196252a14015847517d84effbf2e603b5a6 100644
--- a/core/modules/page_cache/tests/src/Functional/PageCacheTest.php
+++ b/core/modules/page_cache/tests/src/Functional/PageCacheTest.php
@@ -15,6 +15,7 @@
  * Enables the page cache and tests it with various HTTP requests.
  *
  * @group page_cache
+ * @group #slow
  */
 class PageCacheTest extends BrowserTestBase {
 
diff --git a/core/modules/path/tests/src/Functional/GenericTest.php b/core/modules/path/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..83bdc7a1f472838ebe233403597f26331b9b4c0e
--- /dev/null
+++ b/core/modules/path/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\path\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for path.
+ *
+ * @group path
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/path_alias/tests/src/Functional/GenericTest.php b/core/modules/path_alias/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..39e0a389005a1d597d57c10d07677d5d4d6d4f49
--- /dev/null
+++ b/core/modules/path_alias/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\path_alias\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for path_alias.
+ *
+ * @group path_alias
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php
index 6bda0f314778ee1b174e736d61a6604c6b3eaabb..3cab209a0d4800b3aaae540ddc66b23dee801dbe 100644
--- a/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php
+++ b/core/modules/pgsql/src/Driver/Database/pgsql/Connection.php
@@ -11,7 +11,7 @@
 use Drupal\Core\Database\StatementInterface;
 use Drupal\Core\Database\StatementWrapperIterator;
 use Drupal\Core\Database\SupportsTemporaryTablesInterface;
-use Drupal\Core\Database\Transaction;
+use Drupal\Core\Database\Transaction\TransactionManagerInterface;
 
 // cSpell:ignore ilike nextval
 
@@ -72,6 +72,21 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn
    */
   protected $identifierQuotes = ['"', '"'];
 
+  /**
+   * An array of transaction savepoints.
+   *
+   * The main use for this array is to store information about transaction
+   * savepoints opened to to mimic MySql's InnoDB functionality, which provides
+   * an inherent savepoint before any query in a transaction.
+   *
+   * @see ::addSavepoint()
+   * @see ::releaseSavepoint()
+   * @see ::rollbackSavepoint()
+   *
+   * @var array<string,Transaction>
+   */
+  protected array $savepoints = [];
+
   /**
    * Constructs a connection object.
    */
@@ -198,7 +213,7 @@ public function query($query, array $args = [], $options = []) {
     // - A 'mimic_implicit_commit' does not exist already.
     // - The query is not a savepoint query.
     $wrap_with_savepoint = $this->inTransaction() &&
-      !isset($this->transactionLayers['mimic_implicit_commit']) &&
+      !$this->transactionManager()->has('mimic_implicit_commit') &&
       !(is_string($query) && (
         stripos($query, 'ROLLBACK TO SAVEPOINT ') === 0 ||
         stripos($query, 'RELEASE SAVEPOINT ') === 0 ||
@@ -271,22 +286,34 @@ public function databaseType() {
   public function createDatabase($database) {
     // Escape the database name.
     $database = Database::getConnection()->escapeDatabase($database);
-
-    // If the PECL intl extension is installed, use it to determine the proper
-    // locale.  Otherwise, fall back to en_US.
-    if (class_exists('Locale')) {
-      $locale = \Locale::getDefault();
-    }
-    else {
-      $locale = 'en_US';
+    $db_created = FALSE;
+
+    // Try to determine the proper locales for character classification and
+    // collation. If we could determine locales other than 'en_US', try creating
+    // the database with these first.
+    $ctype = setlocale(LC_CTYPE, 0);
+    $collate = setlocale(LC_COLLATE, 0);
+    if ($ctype && $collate) {
+      try {
+        $this->connection->exec("CREATE DATABASE $database WITH TEMPLATE template0 ENCODING='UTF8' LC_CTYPE='$ctype.UTF-8' LC_COLLATE='$collate.UTF-8'");
+        $db_created = TRUE;
+      }
+      catch (\Exception $e) {
+        // It might be that the server is remote and does not support the
+        // locale and collation of the webserver, so we will try again.
+      }
     }
 
-    try {
-      // Create the database and set it as active.
-      $this->connection->exec("CREATE DATABASE $database WITH TEMPLATE template0 ENCODING='utf8' LC_CTYPE='$locale.utf8' LC_COLLATE='$locale.utf8'");
-    }
-    catch (\Exception $e) {
-      throw new DatabaseNotFoundException($e->getMessage());
+    // Otherwise fall back to creating the database using the 'en_US' locales.
+    if (!$db_created) {
+      try {
+        $this->connection->exec("CREATE DATABASE $database WITH TEMPLATE template0 ENCODING='UTF8' LC_CTYPE='en_US.UTF-8' LC_COLLATE='en_US.UTF-8'");
+      }
+      catch (\Exception $e) {
+        // If the database can't be created with the 'en_US' locale either,
+        // we're finally throwing an exception.
+        throw new DatabaseNotFoundException($e->getMessage());
+      }
     }
   }
 
@@ -380,12 +407,10 @@ public function getFullQualifiedTableName($table) {
    * @param $savepoint_name
    *   A string representing the savepoint name. By default,
    *   "mimic_implicit_commit" is used.
-   *
-   * @see Drupal\Core\Database\Connection::pushTransaction()
    */
   public function addSavepoint($savepoint_name = 'mimic_implicit_commit') {
     if ($this->inTransaction()) {
-      $this->pushTransaction($savepoint_name);
+      $this->savepoints[$savepoint_name] = $this->startTransaction($savepoint_name);
     }
   }
 
@@ -395,12 +420,10 @@ public function addSavepoint($savepoint_name = 'mimic_implicit_commit') {
    * @param $savepoint_name
    *   A string representing the savepoint name. By default,
    *   "mimic_implicit_commit" is used.
-   *
-   * @see Drupal\Core\Database\Connection::popTransaction()
    */
   public function releaseSavepoint($savepoint_name = 'mimic_implicit_commit') {
-    if (isset($this->transactionLayers[$savepoint_name])) {
-      $this->popTransaction($savepoint_name);
+    if ($this->inTransaction() && $this->transactionManager()->has($savepoint_name)) {
+      unset($this->savepoints[$savepoint_name]);
     }
   }
 
@@ -412,8 +435,9 @@ public function releaseSavepoint($savepoint_name = 'mimic_implicit_commit') {
    *   "mimic_implicit_commit" is used.
    */
   public function rollbackSavepoint($savepoint_name = 'mimic_implicit_commit') {
-    if (isset($this->transactionLayers[$savepoint_name])) {
-      $this->rollBack($savepoint_name);
+    if ($this->inTransaction() && $this->transactionManager()->has($savepoint_name)) {
+      $this->savepoints[$savepoint_name]->rollBack();
+      unset($this->savepoints[$savepoint_name]);
     }
   }
 
@@ -502,11 +526,18 @@ public function condition($conjunction) {
     return new Condition($conjunction);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function driverTransactionManager(): TransactionManagerInterface {
+    return new TransactionManager($this);
+  }
+
   /**
    * {@inheritdoc}
    */
   public function startTransaction($name = '') {
-    return new Transaction($this, $name);
+    return $this->transactionManager()->push($name);
   }
 
 }
diff --git a/core/modules/pgsql/src/Driver/Database/pgsql/TransactionManager.php b/core/modules/pgsql/src/Driver/Database/pgsql/TransactionManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..4374ac993c3b2e991e302cad1143b8c2d28a2b88
--- /dev/null
+++ b/core/modules/pgsql/src/Driver/Database/pgsql/TransactionManager.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\pgsql\Driver\Database\pgsql;
+
+use Drupal\Core\Database\Transaction\ClientConnectionTransactionState;
+use Drupal\Core\Database\Transaction\TransactionManagerBase;
+
+/**
+ * PostgreSql implementation of TransactionManagerInterface.
+ */
+class TransactionManager extends TransactionManagerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function beginClientTransaction(): bool {
+    return $this->connection->getClientConnection()->beginTransaction();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function rollbackClientTransaction(): bool {
+    $clientRollback = $this->connection->getClientConnection()->rollBack();
+    $this->setConnectionTransactionState($clientRollback ?
+      ClientConnectionTransactionState::RolledBack :
+      ClientConnectionTransactionState::RollbackFailed
+    );
+    return $clientRollback;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function commitClientTransaction(): bool {
+    $clientCommit = $this->connection->getClientConnection()->commit();
+    $this->setConnectionTransactionState($clientCommit ?
+      ClientConnectionTransactionState::Committed :
+      ClientConnectionTransactionState::CommitFailed
+    );
+    return $clientCommit;
+  }
+
+}
diff --git a/core/modules/pgsql/tests/src/Functional/GenericTest.php b/core/modules/pgsql/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..eba843d03df2dce12862542c2a5fed823f9bd172
--- /dev/null
+++ b/core/modules/pgsql/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\pgsql\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for pgsql.
+ *
+ * @group pgsql
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/phpass/tests/src/Functional/GenericTest.php b/core/modules/phpass/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7036462f024ca6fe6dd92611641f99e68dce08cb
--- /dev/null
+++ b/core/modules/phpass/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\phpass\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for phpass.
+ *
+ * @group phpass
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/responsive_image/src/ResponsiveImageConfigUpdater.php b/core/modules/responsive_image/src/ResponsiveImageConfigUpdater.php
index 0e1f09dee93ca11e2e17afdac53afb9b7eefc900..a920798b5bffe7de90c02903737e3cf2ac9ef893 100644
--- a/core/modules/responsive_image/src/ResponsiveImageConfigUpdater.php
+++ b/core/modules/responsive_image/src/ResponsiveImageConfigUpdater.php
@@ -64,7 +64,7 @@ public function orderMultipliersNumerically(ResponsiveImageStyleInterface $respo
     $deprecations_triggered = &$this->triggeredDeprecations['3267870'][$responsive_image_style->id()];
     if ($this->deprecationsEnabled && $changed && !$deprecations_triggered) {
       $deprecations_triggered = TRUE;
-      @trigger_error(sprintf('The responsive image style multiplier re-ordering update for "%s" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided Responsive Image configuration should be updated to accommodate the changes described at https://www.drupal.org/node/3274803.', $responsive_image_style->id()), E_USER_DEPRECATED);
+      @trigger_error(sprintf('The responsive image style multiplier re-ordering update for "%s" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided Responsive Image configuration should be updated. See https://www.drupal.org/node/3274803', $responsive_image_style->id()), E_USER_DEPRECATED);
     }
 
     return $changed;
@@ -96,7 +96,7 @@ public function processResponsiveImageField(EntityViewDisplayInterface $view_dis
     $deprecations_triggered = &$this->triggeredDeprecations['3192234'][$view_display->id()];
     if ($this->deprecationsEnabled && $changed && !$deprecations_triggered) {
       $deprecations_triggered = TRUE;
-      @trigger_error(sprintf('The responsive image loading attribute update for "%s" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Configuration should be updated to accommodate the changes described at https://www.drupal.org/node/3279032.', $view_display->id()), E_USER_DEPRECATED);
+      @trigger_error(sprintf('The responsive image loading attribute update for "%s" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Configuration should be updated. See https://www.drupal.org/node/3279032', $view_display->id()), E_USER_DEPRECATED);
     }
 
     return $changed;
diff --git a/core/modules/responsive_image/tests/src/Functional/GenericTest.php b/core/modules/responsive_image/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..83385caf8e87ee82391b2554f20fbff491147c3f
--- /dev/null
+++ b/core/modules/responsive_image/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\responsive_image\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for responsive_image.
+ *
+ * @group responsive_image
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php
index a86ad20e203f7c5082c0dd735508cd613e66f2d9..6a2a42eff1696135560481c426e50bb0a372cf4e 100644
--- a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php
+++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageFieldDisplayTest.php
@@ -17,6 +17,7 @@
  * Tests responsive image display formatter.
  *
  * @group responsive_image
+ * @group #slow
  */
 class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
 
diff --git a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageLazyLoadUpdateTest.php b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageLazyLoadUpdateTest.php
index cbf90e75779b44c1016142bebff12608f5acfc86..b16a9747b3617d6731c5ca9a99739a0c2b38f72e 100644
--- a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageLazyLoadUpdateTest.php
+++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageLazyLoadUpdateTest.php
@@ -48,7 +48,7 @@ public function testUpdate(): void {
    * @covers ::processResponsiveImageField
    */
   public function testEntitySave(): void {
-    $this->expectDeprecation('The responsive image loading attribute update for "node.article.default" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Configuration should be updated to accommodate the changes described at https://www.drupal.org/node/3279032.');
+    $this->expectDeprecation('The responsive image loading attribute update for "node.article.default" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Configuration should be updated. See https://www.drupal.org/node/3279032');
     $view_display = EntityViewDisplay::load('node.article.default');
     $this->assertArrayNotHasKey('image_loading', $view_display->toArray()['content']['field_image']['settings']);
 
diff --git a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageOrderMultipliersNumericallyUpdateTest.php b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageOrderMultipliersNumericallyUpdateTest.php
index 1df77192bb1777e9063bc4d12684e3431eeb9e49..0a7b63b44779512a8c9aeb8aec2cc19fff33d362 100644
--- a/core/modules/responsive_image/tests/src/Functional/ResponsiveImageOrderMultipliersNumericallyUpdateTest.php
+++ b/core/modules/responsive_image/tests/src/Functional/ResponsiveImageOrderMultipliersNumericallyUpdateTest.php
@@ -53,7 +53,7 @@ public function testUpdate(): void {
    * @covers ::orderMultipliersNumerically
    */
   public function testEntitySave(): void {
-    $this->expectDeprecation('The responsive image style multiplier re-ordering update for "responsive_image_style" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided Responsive Image configuration should be updated to accommodate the changes described at https://www.drupal.org/node/3274803.');
+    $this->expectDeprecation('The responsive image style multiplier re-ordering update for "responsive_image_style" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided Responsive Image configuration should be updated. See https://www.drupal.org/node/3274803');
     $image_style = ResponsiveImageStyle::load('responsive_image_style');
     $mappings = $image_style->getImageStyleMappings();
     $this->assertEquals('1.5x', $mappings[0]['multiplier']);
diff --git a/core/modules/rest/tests/src/Functional/GenericTest.php b/core/modules/rest/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..74eeceb9c24973f6652fd9c5ed892fb4c96643ec
--- /dev/null
+++ b/core/modules/rest/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\rest\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for rest.
+ *
+ * @group rest
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/rest/tests/src/Functional/Views/StyleSerializerTest.php b/core/modules/rest/tests/src/Functional/Views/StyleSerializerTest.php
index c60693a4dea20bc25d84c523d67cdc146f5cc0d7..7c72ea90ed2d57d718b68fe176b96e25594fca68 100644
--- a/core/modules/rest/tests/src/Functional/Views/StyleSerializerTest.php
+++ b/core/modules/rest/tests/src/Functional/Views/StyleSerializerTest.php
@@ -21,6 +21,7 @@
  * Tests the serializer style plugin.
  *
  * @group rest
+ * @group #slow
  * @see \Drupal\rest\Plugin\views\display\RestExport
  * @see \Drupal\rest\Plugin\views\style\Serializer
  * @see \Drupal\rest\Plugin\views\row\DataEntityRow
diff --git a/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php b/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php
index 50591f250741dd0c3f54dad60ad0c7d45a1881f9..b0126a2f1f25a0b78018e3f1115f7ac244f9942b 100644
--- a/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php
+++ b/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php
@@ -222,8 +222,7 @@ public function providerTestResponseFormat() {
 
     $safe_method_test_cases = [
       'safe methods: client requested format (JSON)' => [
-        // @todo add 'HEAD' in https://www.drupal.org/node/2752325
-        ['GET'],
+        ['GET', 'HEAD'],
         ['xml', 'json'],
         [],
         'json',
@@ -234,8 +233,7 @@ public function providerTestResponseFormat() {
         $json_encoded,
       ],
       'safe methods: client requested format (XML)' => [
-        // @todo add 'HEAD' in https://www.drupal.org/node/2752325
-        ['GET'],
+        ['GET', 'HEAD'],
         ['xml', 'json'],
         [],
         'xml',
@@ -246,8 +244,7 @@ public function providerTestResponseFormat() {
         $xml_encoded,
       ],
       'safe methods: client requested no format: response should use the first configured format (JSON)' => [
-        // @todo add 'HEAD' in https://www.drupal.org/node/2752325
-        ['GET'],
+        ['GET', 'HEAD'],
         ['json', 'xml'],
         [],
         FALSE,
@@ -258,8 +255,7 @@ public function providerTestResponseFormat() {
         $json_encoded,
       ],
       'safe methods: client requested no format: response should use the first configured format (XML)' => [
-        // @todo add 'HEAD' in https://www.drupal.org/node/2752325
-        ['GET'],
+        ['GET', 'HEAD'],
         ['xml', 'json'],
         [],
         FALSE,
diff --git a/core/modules/sdc/tests/src/Functional/GenericTest.php b/core/modules/sdc/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f302913ca0d245f810ab383d12432ed78a165084
--- /dev/null
+++ b/core/modules/sdc/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\sdc\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for sdc.
+ *
+ * @group sdc
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/search/src/Entity/SearchPage.php b/core/modules/search/src/Entity/SearchPage.php
index 91cc93960f9f156c485be8faf6b185954790acfc..d5eef74a9cf4845d533e46fe2fb6ca8b07d9731a 100644
--- a/core/modules/search/src/Entity/SearchPage.php
+++ b/core/modules/search/src/Entity/SearchPage.php
@@ -208,7 +208,7 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
   }
 
   /**
-   * {@inheritdoc}
+   * Helper callback for uasort() to sort search page entities by status, weight and label.
    */
   public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
     /** @var \Drupal\search\SearchPageInterface $a */
diff --git a/core/modules/search/src/SearchPageListBuilder.php b/core/modules/search/src/SearchPageListBuilder.php
index 8b844a6b5e488988bd72db6d90103ee4e70672b5..85e2be248ea164027aa137effcda2846e84bbbc8 100644
--- a/core/modules/search/src/SearchPageListBuilder.php
+++ b/core/modules/search/src/SearchPageListBuilder.php
@@ -226,19 +226,17 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     ];
     $form['indexing_throttle']['cron_limit'] = [
       '#type' => 'select',
-      '#title' => $this->t('Number of items to index per cron run'),
+      '#title' => $this->t('Number of items to index per run'),
       '#default_value' => $search_settings->get('index.cron_limit'),
       '#options' => $items,
-      '#description' => $this->t('The maximum number of items indexed in each run of the <a href=":cron">cron maintenance task</a>. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing. Some search page types may have their own setting for this.', [':cron' => Url::fromRoute('system.cron_settings')->toString()]),
+      '#description' => $this->t('The maximum number of items processed per indexing run. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing. Some search page types may have their own setting for this.'),
     ];
     // Indexing settings:
     $form['indexing_settings'] = [
       '#type' => 'details',
       '#title' => $this->t('Default indexing settings'),
       '#open' => TRUE,
-    ];
-    $form['indexing_settings']['info'] = [
-      '#markup' => $this->t("<p>Search pages that use an index may use the default index provided by the Search module, or they may use a different indexing mechanism. These settings are for the default index. <em>Changing these settings will cause the default search index to be rebuilt to reflect the new settings. Searching will continue to work, based on the existing index, but new content won't be indexed until all existing content has been re-indexed.</em></p><p><em>The default settings should be appropriate for the majority of sites.</em></p>"),
+      '#description' => $this->t('Changing these settings will cause the default search index to be rebuilt to reflect the new settings. Searching will continue to work, based on the existing index, but new content will not be indexed until all existing content has been re-indexed.'),
     ];
     $form['indexing_settings']['minimum_word_size'] = [
       '#type' => 'number',
diff --git a/core/modules/search/tests/src/Functional/GenericTest.php b/core/modules/search/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9dfa22686189867b8ae1690afccd6e5ae9aa224
--- /dev/null
+++ b/core/modules/search/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\search\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for search.
+ *
+ * @group search
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php b/core/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php
index 23a7de2d787c8ff6613f39ac14d53f1de5e1628b..1d8c15306f3fd558aeb04af3fc8602f5a3ff5476 100644
--- a/core/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php
+++ b/core/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php
@@ -157,7 +157,7 @@ public function testSearchTagsBubbling() {
     ]);
     $this->drupalLogin($admin_user);
 
-    $this->fieldUIAddNewField($bundle_path, 'test__ref', 'Test label', 'entity_reference', [], [], FALSE);
+    $this->fieldUIAddNewField($bundle_path, 'test__ref', 'Test label', 'entity_reference', [], ['settings[handler_settings][target_bundles][page]' => TRUE]);
     // Create a new node of our newly created node type and fill in the entity
     // reference field.
     $edit = [
diff --git a/core/modules/search/tests/src/Kernel/SearchTextProcessorTest.php b/core/modules/search/tests/src/Kernel/SearchTextProcessorTest.php
index 63f3fcf8286079ab68223699b43e3ab2c3b289c1..066edcb3fc5635ed4063627e4d90fb59ef7cc49b 100644
--- a/core/modules/search/tests/src/Kernel/SearchTextProcessorTest.php
+++ b/core/modules/search/tests/src/Kernel/SearchTextProcessorTest.php
@@ -46,14 +46,14 @@ public function testSearchTextProcessorUnicode() {
         // \Drupal\search\SearchTextProcessorInterface::analyze().
         $start = 0;
         while ($start < mb_strlen($string)) {
-          $newstr = mb_substr($string, $start, 30);
+          $new_string = mb_substr($string, $start, 30);
           // Special case: leading zeros are removed from numeric strings,
           // and there's one string in this file that is numbers starting with
           // zero, so prepend a 1 on that string.
-          if (preg_match('/^[0-9]+$/', $newstr)) {
-            $newstr = '1' . $newstr;
+          if (preg_match('/^[0-9]+$/', $new_string)) {
+            $new_string = '1' . $new_string;
           }
-          $strings[] = $newstr;
+          $strings[] = $new_string;
           $start += 30;
         }
       }
diff --git a/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php b/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php
index 62d244534157507e2edb8a4ec8d4a251db88cdb8..0781e2989529c852f57e918de6fd78b1e5be106c 100644
--- a/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php
+++ b/core/modules/search/tests/src/Unit/SearchPageRepositoryTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\search\Unit;
 
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\search\Entity\SearchPage;
 use Drupal\search\SearchPageRepository;
@@ -252,26 +253,62 @@ public function testSetDefaultSearchPage() {
    * Tests the sortSearchPages() method.
    */
   public function testSortSearchPages() {
-    $entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface');
-    $entity_type->expects($this->any())
+    $entity_type = $this->createMock(EntityTypeInterface::class);
+    $entity_type
       ->method('getClass')
-      ->willReturn('Drupal\Tests\search\Unit\TestSearchPage');
+      ->willReturn(TestSearchPage::class);
     $this->storage->expects($this->once())
       ->method('getEntityType')
       ->willReturn($entity_type);
 
-    // Declare entities out of their expected order so we can be sure they were
-    // sorted. We cannot mock these because of uasort(), see
-    // https://bugs.php.net/bug.php?id=50688.
-    $unsorted_entities['test4'] = new TestSearchPage(['weight' => 0, 'status' => FALSE, 'label' => 'Test4']);
-    $unsorted_entities['test3'] = new TestSearchPage(['weight' => 10, 'status' => TRUE, 'label' => 'Test3']);
-    $unsorted_entities['test2'] = new TestSearchPage(['weight' => 0, 'status' => TRUE, 'label' => 'Test2']);
-    $unsorted_entities['test1'] = new TestSearchPage(['weight' => 0, 'status' => TRUE, 'label' => 'Test1']);
-    $expected = $unsorted_entities;
-    ksort($expected);
+    // Declare entities out of their expected order, so we can be sure they were
+    // sorted.
+    $entity_test4 = $this->createMock(TestSearchPage::class);
+    $entity_test4
+      ->method('label')
+      ->willReturn('Test4');
+    $entity_test4
+      ->method('status')
+      ->willReturn(FALSE);
+    $entity_test4
+      ->method('getWeight')
+      ->willReturn(0);
+    $entity_test3 = $this->createMock(TestSearchPage::class);
+    $entity_test3
+      ->method('label')
+      ->willReturn('Test3');
+    $entity_test3
+      ->method('status')
+      ->willReturn(FALSE);
+    $entity_test3
+      ->method('getWeight')
+      ->willReturn(10);
+    $entity_test2 = $this->createMock(TestSearchPage::class);
+    $entity_test2
+      ->method('label')
+      ->willReturn('Test2');
+    $entity_test2
+      ->method('status')
+      ->willReturn(TRUE);
+    $entity_test2
+      ->method('getWeight')
+      ->willReturn(0);
+    $entity_test1 = $this->createMock(TestSearchPage::class);
+    $entity_test1
+      ->method('label')
+      ->willReturn('Test1');
+    $entity_test1
+      ->method('status')
+      ->willReturn(TRUE);
+    $entity_test1
+      ->method('getWeight')
+      ->willReturn(0);
+
+    $unsorted_entities = [$entity_test4, $entity_test3, $entity_test2, $entity_test1];
+    $expected = [$entity_test1, $entity_test2, $entity_test3, $entity_test4];
 
     $sorted_entities = $this->searchPageRepository->sortSearchPages($unsorted_entities);
-    $this->assertSame($expected, $sorted_entities);
+    $this->assertSame($expected, array_values($sorted_entities));
   }
 
 }
diff --git a/core/modules/serialization/tests/src/Functional/GenericTest.php b/core/modules/serialization/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cc9a29cf00c82c1c4debeaa6ea6c063e489e20c4
--- /dev/null
+++ b/core/modules/serialization/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\serialization\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for serialization.
+ *
+ * @group serialization
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/settings_tray/tests/src/Functional/GenericTest.php b/core/modules/settings_tray/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f46af7c0000c095d269c1b1ecc7c2e553e110d24
--- /dev/null
+++ b/core/modules/settings_tray/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\settings_tray\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for settings_tray.
+ *
+ * @group settings_tray
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/shortcut/tests/src/Functional/GenericTest.php b/core/modules/shortcut/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..399acc621b48909054b3d816d76648605ef2a94e
--- /dev/null
+++ b/core/modules/shortcut/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\shortcut\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for shortcut.
+ *
+ * @group shortcut
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php
index 0c14efb20f7965029946dff0e331bf84cff017f2..31622c35a841d5ebf24c3eed84dd948656f12b4d 100644
--- a/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php
+++ b/core/modules/sqlite/src/Driver/Database/sqlite/Connection.php
@@ -9,7 +9,7 @@
 use Drupal\Core\Database\Query\Condition;
 use Drupal\Core\Database\StatementInterface;
 use Drupal\Core\Database\SupportsTemporaryTablesInterface;
-use Drupal\Core\Database\Transaction;
+use Drupal\Core\Database\Transaction\TransactionManagerInterface;
 
 /**
  * SQLite implementation of \Drupal\Core\Database\Connection.
@@ -30,6 +30,11 @@ class Connection extends DatabaseConnection implements SupportsTemporaryTablesIn
    * Whether or not the active transaction (if any) will be rolled back.
    *
    * @var bool
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. It is
+   *   unused.
+   *
+   * @see https://www.drupal.org/node/3381002
    */
   protected $willRollback;
 
@@ -584,11 +589,18 @@ public function condition($conjunction) {
     return new Condition($conjunction);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function driverTransactionManager(): TransactionManagerInterface {
+    return new TransactionManager($this);
+  }
+
   /**
    * {@inheritdoc}
    */
   public function startTransaction($name = '') {
-    return new Transaction($this, $name);
+    return $this->transactionManager()->push($name);
   }
 
 }
diff --git a/core/modules/sqlite/src/Driver/Database/sqlite/TransactionManager.php b/core/modules/sqlite/src/Driver/Database/sqlite/TransactionManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b422767d63f2aecd8e4b49e81d1714d003612c2
--- /dev/null
+++ b/core/modules/sqlite/src/Driver/Database/sqlite/TransactionManager.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\sqlite\Driver\Database\sqlite;
+
+use Drupal\Core\Database\Transaction\ClientConnectionTransactionState;
+use Drupal\Core\Database\Transaction\TransactionManagerBase;
+
+/**
+ * SQLite implementation of TransactionManagerInterface.
+ */
+class TransactionManager extends TransactionManagerBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function beginClientTransaction(): bool {
+    return $this->connection->getClientConnection()->beginTransaction();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function rollbackClientTransaction(): bool {
+    $clientRollback = $this->connection->getClientConnection()->rollBack();
+    $this->setConnectionTransactionState($clientRollback ?
+      ClientConnectionTransactionState::RolledBack :
+      ClientConnectionTransactionState::RollbackFailed
+    );
+    return $clientRollback;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function commitClientTransaction(): bool {
+    $clientCommit = $this->connection->getClientConnection()->commit();
+    $this->setConnectionTransactionState($clientCommit ?
+      ClientConnectionTransactionState::Committed :
+      ClientConnectionTransactionState::CommitFailed
+    );
+    return $clientCommit;
+  }
+
+}
diff --git a/core/modules/sqlite/tests/src/Functional/GenericTest.php b/core/modules/sqlite/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6857183372fe401f80d61d860ac947bf457212fd
--- /dev/null
+++ b/core/modules/sqlite/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\sqlite\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for sqlite.
+ *
+ * @group sqlite
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/statistics/src/StatisticsSettingsForm.php b/core/modules/statistics/src/StatisticsSettingsForm.php
index 49b6a6dacc94fbc3044e3c2f4ee4815657c2ab8a..51218583edc377742abafb3cb3011b31667f3fa2 100644
--- a/core/modules/statistics/src/StatisticsSettingsForm.php
+++ b/core/modules/statistics/src/StatisticsSettingsForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\statistics;
 
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Config\ConfigFactoryInterface;
@@ -27,11 +28,13 @@ class StatisticsSettingsForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler) {
+    parent::__construct($config_factory, $typedConfigManager);
 
     $this->moduleHandler = $module_handler;
   }
@@ -42,6 +45,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('module_handler')
     );
   }
diff --git a/core/modules/statistics/tests/src/Functional/GenericTest.php b/core/modules/statistics/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..281fc907ec8c7f080d956e31f0abaace03ffd13f
--- /dev/null
+++ b/core/modules/statistics/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\statistics\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for statistics.
+ *
+ * @group statistics
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/syslog/tests/src/Functional/GenericTest.php b/core/modules/syslog/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ab4a970d5dd1b86dfd2a749efe9b90f255c98bf8
--- /dev/null
+++ b/core/modules/syslog/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\syslog\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for syslog.
+ *
+ * @group syslog
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php b/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..a527605ea737cfdac56e58992459ac1e2d0f644c
--- /dev/null
+++ b/core/modules/system/src/Access/SystemAdminMenuBlockAccessCheck.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Drupal\system\Access;
+
+use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\AccessAwareRouter;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Access check for routes implementing _access_admin_menu_block_page.
+ *
+ * @see \Drupal\system\EventSubscriber\AccessRouteAlterSubscriber
+ * @see \Drupal\system\Controller\SystemController::systemAdminMenuBlockPage()
+ */
+class SystemAdminMenuBlockAccessCheck implements AccessInterface {
+
+  /**
+   * Constructs a new SystemAdminMenuBlockAccessCheck.
+   *
+   * @param \Drupal\Core\Access\AccessManagerInterface $accessManager
+   *   The access manager.
+   * @param \Drupal\Core\Menu\MenuLinkTreeInterface $menuLinkTree
+   *   The menu link tree service.
+   * @param \Drupal\Core\Routing\AccessAwareRouter $router
+   *   The router service.
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menuLinkManager
+   *   The menu link manager service.
+   */
+  public function __construct(
+    private readonly AccessManagerInterface $accessManager,
+    private readonly MenuLinkTreeInterface $menuLinkTree,
+    private readonly AccessAwareRouter $router,
+    private readonly MenuLinkManagerInterface $menuLinkManager,
+  ) {
+  }
+
+  /**
+   * Checks access.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The cron key.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The current user.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(RouteMatchInterface $route_match, AccountInterface $account): AccessResultInterface {
+    $parameters = $route_match->getParameters()->all();
+    // Load links in the 'admin' menu matching this route.
+    $links = $this->menuLinkManager->loadLinksByRoute($route_match->getRouteName(), $parameters, 'admin');
+    if (empty($links)) {
+      // If we did not find a link then we have no opinion on access.
+      return AccessResult::neutral();
+    }
+    return $this->hasAccessToChildMenuItems(reset($links), $account)->cachePerPermissions();
+  }
+
+  /**
+   * Check that the given route has access to one of it's child routes.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkInterface $link
+   *   The menu link.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The account.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  protected function hasAccessToChildMenuItems(MenuLinkInterface $link, AccountInterface $account): AccessResultInterface {
+    $parameters = new MenuTreeParameters();
+    $parameters->setRoot($link->getPluginId())
+      ->excludeRoot()
+      ->setTopLevelOnly()
+      ->onlyEnabledLinks();
+
+    $tree = $this->menuLinkTree->load(NULL, $parameters);
+
+    if (empty($tree)) {
+      $route = $this->router->getRouteCollection()->get($link->getRouteName());
+      if ($route) {
+        return AccessResult::allowedIf(empty($route->getRequirement('_access_admin_menu_block_page')));
+      }
+      return AccessResult::neutral();
+    }
+
+    foreach ($tree as $element) {
+      if (!$this->accessManager->checkNamedRoute($element->link->getRouteName(), $element->link->getRouteParameters(), $account)) {
+        continue;
+      }
+
+      // If access is allowed to this element in the tree check for access to
+      // its own children.
+      return AccessResult::allowedIf($this->hasAccessToChildMenuItems($element->link, $account)->isAllowed());
+    }
+    return AccessResult::neutral();
+  }
+
+}
diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php
index 8a9d3dbc45ae1a95e8dcfcb5056620a545532aa7..afb715dad9a142cedebaf616cb87527e6d02f0f4 100644
--- a/core/modules/system/src/Controller/SystemController.php
+++ b/core/modules/system/src/Controller/SystemController.php
@@ -109,6 +109,9 @@ public static function create(ContainerInterface $container) {
   /**
    * Provide the administration overview page.
    *
+   * This will render child links two levels below the specified link ID,
+   * grouped by the child links one level below.
+   *
    * @param string $link_id
    *   The ID of the administrative path link for which to display child links.
    *
diff --git a/core/modules/system/src/EventSubscriber/AccessRouteAlterSubscriber.php b/core/modules/system/src/EventSubscriber/AccessRouteAlterSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..5bc438442bc45fc311aa27cc40bde27afdf835e7
--- /dev/null
+++ b/core/modules/system/src/EventSubscriber/AccessRouteAlterSubscriber.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\system\EventSubscriber;
+
+use Drupal\Core\Routing\RouteBuildEvent;
+use Drupal\Core\Routing\RoutingEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Alters routes to add necessary requirements.
+ *
+ * @see \Drupal\system\Access\SystemAdminMenuBlockAccessCheck
+ * @see \Drupal\system\Controller\SystemController::systemAdminMenuBlockPage()
+ */
+class AccessRouteAlterSubscriber implements EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[RoutingEvents::ALTER][] = 'accessAdminMenuBlockPage';
+    return $events;
+  }
+
+  /**
+   * Adds _access_admin_menu_block_page requirement to routes pointing to SystemController::systemAdminMenuBlockPage.
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The event to process.
+   */
+  public function accessAdminMenuBlockPage(RouteBuildEvent $event) {
+    $routes = $event->getRouteCollection();
+    foreach ($routes as $route) {
+      // Do not use a leading slash when comparing to the _controller string
+      // because the leading slash in a fully-qualified method name is optional.
+      if ($route->hasDefault('_controller') && ltrim($route->getDefault('_controller'), '\\') === 'Drupal\system\Controller\SystemController::systemAdminMenuBlockPage') {
+        $route->setRequirement('_access_admin_menu_block_page', 'TRUE');
+      }
+    }
+  }
+
+}
diff --git a/core/modules/system/src/EventSubscriber/ConfigCacheTag.php b/core/modules/system/src/EventSubscriber/ConfigCacheTag.php
index 5a0f6aa740b1c014b4e0ccdc77d917cf04ba6a90..06fdde86103c7c9caff021b9fed1453634a73891 100644
--- a/core/modules/system/src/EventSubscriber/ConfigCacheTag.php
+++ b/core/modules/system/src/EventSubscriber/ConfigCacheTag.php
@@ -42,7 +42,7 @@ public function __construct(ThemeHandlerInterface $theme_handler, CacheTagsInval
     $this->themeHandler = $theme_handler;
     $this->cacheTagsInvalidator = $cache_tags_invalidator;
     if ($this->themeRegistry === NULL) {
-      @trigger_error('Calling ' . __METHOD__ . '() without the $themeRegistry argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0.', E_USER_DEPRECATED);
+      @trigger_error('Calling ' . __METHOD__ . '() without the $themeRegistry argument is deprecated in drupal:10.2.0 and will be required in drupal:11.0.0. See https://www.drupal.org/node/3355227', E_USER_DEPRECATED);
       $this->themeRegistry = \Drupal::service('theme.registry');
     }
   }
diff --git a/core/modules/system/src/Form/FileSystemForm.php b/core/modules/system/src/Form/FileSystemForm.php
index 18c1cb8ce160b0ddcbfb20c792adee6f2a0f1e80..f0b4391724ed86f2f3d4f4dca24c760c65a68645 100644
--- a/core/modules/system/src/Form/FileSystemForm.php
+++ b/core/modules/system/src/Form/FileSystemForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\system\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Form\FormStateInterface;
@@ -47,6 +48,8 @@ class FileSystemForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
    *   The date formatter service.
    * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
@@ -54,8 +57,8 @@ class FileSystemForm extends ConfigFormBase {
    * @param \Drupal\Core\File\FileSystemInterface $file_system
    *   The file system.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, DateFormatterInterface $date_formatter, StreamWrapperManagerInterface $stream_wrapper_manager, FileSystemInterface $file_system) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, DateFormatterInterface $date_formatter, StreamWrapperManagerInterface $stream_wrapper_manager, FileSystemInterface $file_system) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->dateFormatter = $date_formatter;
     $this->streamWrapperManager = $stream_wrapper_manager;
     $this->fileSystem = $file_system;
@@ -67,6 +70,7 @@ public function __construct(ConfigFactoryInterface $config_factory, DateFormatte
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('date.formatter'),
       $container->get('stream_wrapper_manager'),
       $container->get('file_system')
diff --git a/core/modules/system/src/Form/ImageToolkitForm.php b/core/modules/system/src/Form/ImageToolkitForm.php
index f11f695a42afe15f86354176e30d184152273a83..adbbd4c7c5ddf6881e09d892641f07f16ced3584 100644
--- a/core/modules/system/src/Form/ImageToolkitForm.php
+++ b/core/modules/system/src/Form/ImageToolkitForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\system\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\ImageToolkit\ImageToolkitManager;
@@ -27,11 +28,13 @@ class ImageToolkitForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\ImageToolkit\ImageToolkitManager $manager
    *   The image toolkit plugin manager.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ImageToolkitManager $manager) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ImageToolkitManager $manager) {
+    parent::__construct($config_factory, $typedConfigManager);
 
     foreach ($manager->getAvailableToolkits() as $id => $definition) {
       $this->availableToolkits[$id] = $manager->createInstance($id);
@@ -44,6 +47,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ImageToolkit
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('image.toolkit.manager')
     );
   }
diff --git a/core/modules/system/src/Form/MenuLinksetSettingsForm.php b/core/modules/system/src/Form/MenuLinksetSettingsForm.php
index fd7663612e9eb2c119e549969c7d60af9658c29d..4353a27572d62ec2286c84fc569659baf7ac2117 100644
--- a/core/modules/system/src/Form/MenuLinksetSettingsForm.php
+++ b/core/modules/system/src/Form/MenuLinksetSettingsForm.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\system\Form;
 
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteBuilderInterface;
@@ -13,12 +15,21 @@
 class MenuLinksetSettingsForm extends ConfigFormBase {
 
   /**
-   * Constructs the routerBuilder service.
+   * Constructs a MenuLinksetSettingsForm object.
    *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Routing\RouteBuilderInterface $routerBuilder
    *   The router builder service.
    */
-  public function __construct(protected readonly RouteBuilderInterface $routerBuilder) {
+  public function __construct(
+    ConfigFactoryInterface $config_factory,
+    TypedConfigManagerInterface $typedConfigManager,
+    protected readonly RouteBuilderInterface $routerBuilder
+  ) {
+    parent::__construct($config_factory, $typedConfigManager);
   }
 
   /**
@@ -26,6 +37,8 @@ public function __construct(protected readonly RouteBuilderInterface $routerBuil
    */
   public static function create(ContainerInterface $container) {
     return new static(
+      $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('router.builder')
     );
   }
diff --git a/core/modules/system/src/Form/PerformanceForm.php b/core/modules/system/src/Form/PerformanceForm.php
index cf26b6dbf1f3c1a226e1328cd116b22950c24601..7ba2cc7813fd3a5354743f4ae5dfbb153fbbd068 100644
--- a/core/modules/system/src/Form/PerformanceForm.php
+++ b/core/modules/system/src/Form/PerformanceForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\system\Form;
 
 use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Config\ConfigFactoryInterface;
@@ -51,6 +52,8 @@ class PerformanceForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
    *   The date formatter service.
    * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
@@ -60,8 +63,8 @@ class PerformanceForm extends ConfigFormBase {
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, DateFormatterInterface $date_formatter, AssetCollectionOptimizerInterface $css_collection_optimizer, AssetCollectionOptimizerInterface $js_collection_optimizer, ModuleHandlerInterface $module_handler) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, DateFormatterInterface $date_formatter, AssetCollectionOptimizerInterface $css_collection_optimizer, AssetCollectionOptimizerInterface $js_collection_optimizer, ModuleHandlerInterface $module_handler) {
+    parent::__construct($config_factory, $typedConfigManager);
 
     $this->dateFormatter = $date_formatter;
     $this->cssCollectionOptimizer = $css_collection_optimizer;
@@ -75,6 +78,7 @@ public function __construct(ConfigFactoryInterface $config_factory, DateFormatte
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('date.formatter'),
       $container->get('asset.css.collection_optimizer'),
       $container->get('asset.js.collection_optimizer'),
diff --git a/core/modules/system/src/Form/RegionalForm.php b/core/modules/system/src/Form/RegionalForm.php
index f2028ec3fb511123683a23e0470166a10b035c66..bd64a67a8d46c00c429a631cf342c4e65bfd44cc 100644
--- a/core/modules/system/src/Form/RegionalForm.php
+++ b/core/modules/system/src/Form/RegionalForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\system\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Datetime\TimeZoneFormHelper;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Locale\CountryManagerInterface;
@@ -28,11 +29,13 @@ class RegionalForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Locale\CountryManagerInterface $country_manager
    *   The country manager.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, CountryManagerInterface $country_manager) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, CountryManagerInterface $country_manager) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->countryManager = $country_manager;
   }
 
@@ -42,6 +45,7 @@ public function __construct(ConfigFactoryInterface $config_factory, CountryManag
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('country_manager')
     );
   }
diff --git a/core/modules/system/src/Form/SiteInformationForm.php b/core/modules/system/src/Form/SiteInformationForm.php
index 4049f1ff1061f3b1caa8a32448d28f374834fc07..372e4e8d2462cd279ae19260902e854dc8ab0141 100644
--- a/core/modules/system/src/Form/SiteInformationForm.php
+++ b/core/modules/system/src/Form/SiteInformationForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\system\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Path\PathValidatorInterface;
@@ -43,6 +44,8 @@ class SiteInformationForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\path_alias\AliasManagerInterface $alias_manager
    *   The path alias manager.
    * @param \Drupal\Core\Path\PathValidatorInterface $path_validator
@@ -50,8 +53,8 @@ class SiteInformationForm extends ConfigFormBase {
    * @param \Drupal\Core\Routing\RequestContext $request_context
    *   The request context.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->aliasManager = $alias_manager;
     $this->pathValidator = $path_validator;
     $this->requestContext = $request_context;
@@ -63,6 +66,7 @@ public function __construct(ConfigFactoryInterface $config_factory, AliasManager
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('path_alias.manager'),
       $container->get('path.validator'),
       $container->get('router.request_context')
diff --git a/core/modules/system/src/Form/SiteMaintenanceModeForm.php b/core/modules/system/src/Form/SiteMaintenanceModeForm.php
index 359b9abd6331b943a6109823a474a5cc7000228a..d35658464081db2df78eae213e49831465bf41bc 100644
--- a/core/modules/system/src/Form/SiteMaintenanceModeForm.php
+++ b/core/modules/system/src/Form/SiteMaintenanceModeForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\system\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\State\StateInterface;
 use Drupal\Core\Form\ConfigFormBase;
@@ -36,13 +37,15 @@ class SiteMaintenanceModeForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\State\StateInterface $state
    *   The state keyvalue collection to use.
    * @param \Drupal\user\PermissionHandlerInterface $permission_handler
    *   The permission handler.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, StateInterface $state, PermissionHandlerInterface $permission_handler) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, StateInterface $state, PermissionHandlerInterface $permission_handler) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->state = $state;
     $this->permissionHandler = $permission_handler;
   }
@@ -53,6 +56,7 @@ public function __construct(ConfigFactoryInterface $config_factory, StateInterfa
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('state'),
       $container->get('user.permissions')
     );
diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php
index 8ab05bea550391c6dd7ac0b1ffda0bf6227e7b0e..b68fa58cde13e27cdbc6a48df866860c517e7207 100644
--- a/core/modules/system/src/Form/ThemeSettingsForm.php
+++ b/core/modules/system/src/Form/ThemeSettingsForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\system\Form;
 
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\File\Exception\FileException;
 use Drupal\Core\File\FileSystemInterface;
@@ -72,6 +73,8 @@ class ThemeSettingsForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler instance to use.
    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
@@ -83,8 +86,8 @@ class ThemeSettingsForm extends ConfigFormBase {
    * @param \Drupal\Core\File\FileSystemInterface $file_system
    *   The file system.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $mime_type_guesser, ThemeManagerInterface $theme_manager, FileSystemInterface $file_system) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $mime_type_guesser, ThemeManagerInterface $theme_manager, FileSystemInterface $file_system) {
+    parent::__construct($config_factory, $typedConfigManager);
 
     $this->moduleHandler = $module_handler;
     $this->themeHandler = $theme_handler;
@@ -99,6 +102,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('module_handler'),
       $container->get('theme_handler'),
       $container->get('file.mime_type.guesser'),
diff --git a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
index 8f4a112d8bf5763056b494027338d9f936775888..8ba8e9c82f19477fddfc49d6c17af09c3d982a66 100644
--- a/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
+++ b/core/modules/system/src/Plugin/ImageToolkit/GDToolkit.php
@@ -115,7 +115,7 @@ public static function create(ContainerInterface $container, array $configuratio
    */
   public function __get(string $name) {
     if ($name === 'resource') {
-      @trigger_error('Accessing the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead.', E_USER_DEPRECATED);
+      @trigger_error('Accessing the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead. See https://www.drupal.org/node/3265963', E_USER_DEPRECATED);
       return $this->image;
     }
   }
@@ -125,7 +125,7 @@ public function __get(string $name) {
    */
   public function __set(string $name, mixed $value): void {
     if ($name === 'resource') {
-      @trigger_error('Setting the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead.', E_USER_DEPRECATED);
+      @trigger_error('Setting the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead. See https://www.drupal.org/node/3265963', E_USER_DEPRECATED);
       $this->image = $value;
     }
   }
@@ -135,7 +135,7 @@ public function __set(string $name, mixed $value): void {
    */
   public function __isset(string $name): bool {
     if ($name === 'resource') {
-      @trigger_error('Checking the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead.', E_USER_DEPRECATED);
+      @trigger_error('Checking the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead. See https://www.drupal.org/node/3265963', E_USER_DEPRECATED);
       return isset($this->image);
     }
     return FALSE;
@@ -146,7 +146,7 @@ public function __isset(string $name): bool {
    */
   public function __unset(string $name): void {
     if ($name === 'resource') {
-      @trigger_error('Unsetting the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead.', E_USER_DEPRECATED);
+      @trigger_error('Unsetting the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead. See https://www.drupal.org/node/3265963', E_USER_DEPRECATED);
       unset($this->image);
     }
   }
diff --git a/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageRevisionDataCleanupTest.php b/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageRevisionDataCleanupTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..81ec430261a567844dec8bbbbc1e8e2e90b2a747
--- /dev/null
+++ b/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageRevisionDataCleanupTest.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\system\Tests\Entity\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use PHPUnit\Framework\Assert;
+
+/**
+ * Tests cleaning up revision data tables.
+ *
+ * @group Entity
+ * @group Update
+ */
+class SqlContentEntityStorageRevisionDataCleanupTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.4.0.filled.standard.php.gz',
+      __DIR__ . '/../../../../tests/fixtures/update/drupal-8.entity-revision-data-cleanup-2869568.php',
+    ];
+  }
+
+  /**
+   * Tests that stale rows in the revision data table are deleted.
+   *
+   * @see system_update_8404()
+   */
+  public function testRevisionDataCleanup() {
+    // Ensure the test data exists.
+    $connection = \Drupal::database();
+
+    // There are 104 rows, 101 rows to delete plus the original 3 valid rows.
+    $result = $connection->query('SELECT nid, vid, langcode FROM {node_field_revision} WHERE nid = :nid', [
+      'nid' => 8,
+    ])->fetchAll();
+    $this->assertCount(104, $result);
+
+    $this->runUpdates();
+
+    // Ensure the correct rows were deleted and only those.
+    $result = $connection->query('SELECT nid, vid FROM {node_field_revision} WHERE nid = :nid AND vid = :vid ORDER BY nid, vid, langcode DESC', [
+      'nid' => 8,
+      'vid' => 8,
+    ])->fetchAll();
+    $this->assertEmpty($result);
+
+    $result = $connection->query('SELECT nid, vid FROM {node_field_revision} WHERE nid = :nid AND vid = :vid ORDER BY nid, vid, langcode DESC', [
+      'nid' => 8,
+      'vid' => 9,
+    ])->fetchAll();
+    $this->assertEquals($result, [(object) ['nid' => '8', 'vid' => '9']]);
+
+    // Revision 10 has two translations, ensure both records still exist.
+    $result = $connection->query('SELECT nid, vid, langcode FROM {node_field_revision} WHERE nid = :nid AND vid = :vid ORDER BY nid, vid, langcode DESC', [
+      'nid' => 8,
+      'vid' => 10,
+    ])->fetchAll();
+    Assert::assertEquals($result, [
+      (object) [
+        'nid' => '8',
+        'vid' => '10',
+        'langcode' => 'es',
+      ],
+      (object) ['nid' => '8', 'vid' => '10', 'langcode' => 'en'],
+    ]);
+
+    // There should be only 3 rows left.
+    $result = $connection->query('SELECT nid, vid, langcode FROM {node_field_revision} WHERE nid = :nid', [
+      'nid' => 8,
+    ])->fetchAll();
+    $this->assertCount(3, $result);
+  }
+
+}
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 5684accadf2f964a3eeea7f6b31d5ac37d6304ba..ac544cba459211155afcc181b86a66218d940983 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -15,6 +15,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Extension\ExtensionLifecycle;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Utility\PhpRequirements;
@@ -22,6 +23,7 @@
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PrivateStream;
 use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Url;
@@ -422,7 +424,7 @@ function system_requirements($phase) {
     $requirements['php_apcu_available']['title'] = t('PHP APCu available caching');
     if (extension_loaded('apcu') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN)) {
       $memory_info = apcu_sma_info(TRUE);
-      $apcu_actual_size = format_size($memory_info['seg_size']);
+      $apcu_actual_size = ByteSizeMarkup::create($memory_info['seg_size']);
       $apcu_recommended_size = '32 MB';
       $requirements['php_apcu_enabled']['value'] = t('Enabled (@size)', ['@size' => $apcu_actual_size]);
       if (Bytes::toNumber($apcu_actual_size) < Bytes::toNumber($apcu_recommended_size)) {
@@ -450,7 +452,7 @@ function system_requirements($phase) {
           $requirements['php_apcu_available']['severity'] = REQUIREMENT_OK;
         }
         $requirements['php_apcu_available']['value'] = t('Memory available: @available.', [
-          '@available' => format_size($memory_info['avail_mem']),
+          '@available' => ByteSizeMarkup::create($memory_info['avail_mem']),
         ]);
       }
     }
@@ -1845,3 +1847,88 @@ function system_update_10101(&$sandbox = NULL) {
   }
 
 }
+
+/**
+ * Clear left over entries in the revision data table.
+ */
+function system_update_10201(&$sandbox) {
+  $entity_type_manager = \Drupal::entityTypeManager();
+  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  $database = \Drupal::database();
+
+  // Initialize the sandbox. Store a list of entity types that need to be
+  // cleaned up in a list, then keep track of the current entity type by
+  // its numeric index.
+  if (!isset($sandbox['current_key'])) {
+    // This must be the first run. Initialize the sandbox.
+    $sandbox['current_key'] = 0;
+
+    // Filter the entity types for those that need to be cleaned up.
+    $definitions = array_filter($entity_type_manager->getDefinitions(), function (EntityTypeInterface $entity_type) use ($entity_definition_update_manager, $database) {
+
+      // Use the last installed entity type definition for the filtering, as
+      // some might not have been installed yet or are only being made
+      // revisionable or translatable in a later update function.
+      $entity_type = $entity_definition_update_manager->getEntityType($entity_type->id());
+      if (!$entity_type) {
+        // New entity type that is not installed yet, nothing to clean up, skip.
+        return FALSE;
+      }
+      if (!$entity_type->isRevisionable() || !$entity_type->isTranslatable()) {
+        // Entity type is either not revisionable or not translatable.
+        return FALSE;
+      }
+
+      // Ensure the revision and revision data tables really exist.
+      $entity_type_id = $entity_type->id();
+      $revision_table = $entity_type->getRevisionTable() ?: $entity_type_id . '_revision';
+      $revision_data_table = $entity_type->getRevisionDataTable() ?: $entity_type_id . '_field_revision';
+      return $database->schema()
+        ->tableExists($revision_table) && $database->schema()
+        ->tableExists($revision_data_table);
+    });
+
+    if (empty($definitions)) {
+      $sandbox['#finished'] = TRUE;
+      return;
+    }
+
+    $sandbox['entity_type_ids'] = array_keys($definitions);
+    $sandbox['max'] = count($sandbox['entity_type_ids']);
+  }
+
+  // Get the current entity type. Fetch 100 records that should be deleted.
+  // Order them by entity ID to optimize the amount of delete queries that need
+  // to be executed.
+  $entity_type_id = $sandbox['entity_type_ids'][$sandbox['current_key']];
+  /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
+  $entity_type = $entity_definition_update_manager->getEntityType($entity_type_id);
+
+  $revision_table = $entity_type->getRevisionTable() ?: $entity_type_id . '_revision';
+  $revision_data_table = $entity_type->getRevisionDataTable() ?: $entity_type_id . '_field_revision';
+  $revision_field = $entity_type->getKey('revision');
+
+  $query = $database->select($revision_data_table, 'rd');
+  $query->leftJoin($revision_table, 'r', 'r.' . $revision_field . ' = rd.' . $revision_field);
+  $query->addField('rd', $revision_field, 'revision');
+
+  $revision_ids = $query
+    ->isNull('r.' . $revision_field)
+    ->range(0, 100)
+    ->execute()
+    ->fetchCol();
+
+  if ($revision_ids) {
+    $database->delete($revision_data_table)
+      ->condition($revision_field, $revision_ids, 'IN')
+      ->execute();
+  }
+
+  // If there are less than 100 rows, the entity type was cleaned up, move on to
+  // the next one.
+  if (count($revision_ids) < 100) {
+    $sandbox['current_key']++;
+  }
+
+  $sandbox['#finished'] = $sandbox['current_key'] == $sandbox['max'];
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index cb6a45294e45e94fabdcc07af15b46e3e49c8a44..73c5634e4ec489c750f69d28a0a5dba91538eaa8 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1390,7 +1390,7 @@ function system_entity_view_display_presave(EntityViewDisplayInterface $entity_v
     }
 
     if (!isset($component['settings']['tooltip']) || !isset($component['settings']['time_diff'])) {
-      @trigger_error("The 'timestamp' formatter plugin 'tooltip' and 'time_diff' settings were added in drupal:10.1.0 and will be mandatory in Drupal 11.0.0. See https://www.drupal.org/node/2993639", E_USER_DEPRECATED);
+      @trigger_error("Using the 'timestamp' formatter plugin  without the 'tooltip' and 'time_diff' settings is deprecated in drupal:10.1.0 and is required in drupal:11.0.0. See https://www.drupal.org/node/2993639", E_USER_DEPRECATED);
       $entity_view_display->setComponent($name, $component);
     }
   }
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 07de084f03493d7afa31f4afb340ff5c59ef077e..5a7c75e495f69db47e647e4fe78e2d5f81bb4289 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -515,8 +515,7 @@ system.db_update:
 system.admin_content:
   path: '/admin/content'
   defaults:
-    _controller: '\Drupal\system\Controller\SystemController::overview'
-    link_id: 'system.admin_content'
+    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
     _title: 'Content'
   requirements:
     _permission: 'access administration pages'
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index a1050402556983f6b11f1f92e98491bc7da3d4b1..7754d14372852e1957204bcc722d1268537efbda 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -7,6 +7,11 @@ services:
     class: Drupal\system\Access\DbUpdateAccessCheck
     tags:
       - { name: access_check, applies_to: _access_system_update }
+  access_check.admin_menu_block_page:
+    class: Drupal\system\Access\SystemAdminMenuBlockAccessCheck
+    arguments: ['@access_manager', '@menu.link_tree', '@router', '@plugin.manager.menu.link']
+    tags:
+      - { name: access_check, applies_to: _access_admin_menu_block_page }
   system.manager:
     class: Drupal\system\SystemManager
     arguments: ['@module_handler', '@request_stack', '@menu.link_tree', '@menu.active_trail']
@@ -77,3 +82,7 @@ services:
     arguments: ['@menu.link_tree', '@system.module_admin_links_memory_cache']
   system.module_admin_links_memory_cache:
     class: Drupal\Core\Cache\MemoryCache\MemoryCache
+  system.access_route_alter_subscriber:
+    class: Drupal\system\EventSubscriber\AccessRouteAlterSubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/system/templates/block--system-branding-block.html.twig b/core/modules/system/templates/block--system-branding-block.html.twig
index ee4ec847dd9d8bf90962cd580e7243562ed2a9e5..ba579c358f42e982b9517cc193bd9d9da8b5632e 100644
--- a/core/modules/system/templates/block--system-branding-block.html.twig
+++ b/core/modules/system/templates/block--system-branding-block.html.twig
@@ -18,7 +18,7 @@
 {% block content %}
   {% if site_logo %}
     <a href="{{ path('<front>') }}" rel="home">
-      <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" />
+      <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" fetchpriority="high" />
     </a>
   {% endif %}
   {% if site_name %}
diff --git a/core/modules/system/tests/fixtures/update/drupal-8.entity-revision-data-cleanup-2869568.php b/core/modules/system/tests/fixtures/update/drupal-8.entity-revision-data-cleanup-2869568.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ae1996669d9a2a64f4ad01e0133c6a8d99ed3d3
--- /dev/null
+++ b/core/modules/system/tests/fixtures/update/drupal-8.entity-revision-data-cleanup-2869568.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Provides database changes for testing upgrade path of system_update_8404().
+ *
+ * @see \Drupal\Tests\system\Functional\Update\SqlContentEntityStorageRevisionDataCleanupTest
+ */
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Language\LanguageInterface;
+
+$connection = Database::getConnection();
+
+// Manually add a record to the node_revision
+$connection->insert('node_field_revision')
+  ->fields([
+    'nid' => 8,
+    'vid' => 8,
+    'langcode' => 'en',
+    'title' => 'Deleted revision',
+    'uid' => 1,
+    'status' => 1,
+    'created' => 1439731773,
+    'changed' => 1439732036,
+    'promote' => 1,
+    'sticky' => 0,
+    'revision_translation_affected' => 1,
+    'default_langcode' => 1,
+    'content_translation_source' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
+    'content_translation_outdated' => 0,
+  ])
+  ->execute();
+
+// Add 100 more rows to test the loop in system_update_10201.
+for ($i = 1; $i <= 100; $i++) {
+  // Ensure that the new nodes use a vid that is greater than the maximum vid
+  // in drupal-9.4.0.filled.standard.php.gz for node id 8.
+  $vid = 10 + $i;
+  $connection->insert('node_field_revision')
+    ->fields([
+      'nid' => 8,
+      'vid' => $vid,
+      'langcode' => 'en',
+      'title' => 'Deleted revision',
+      'uid' => 1,
+      'status' => 1,
+      'created' => 1439732773,
+      'changed' => 1439733036,
+      'promote' => 1,
+      'sticky' => 0,
+      'revision_translation_affected' => 1,
+      'default_langcode' => 1,
+      'content_translation_source' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
+      'content_translation_outdated' => 0,
+    ])
+    ->execute();
+}
diff --git a/core/modules/system/tests/http.php b/core/modules/system/tests/http.php
index 6d822ec88f0805589828c785ecd5b4d5f9fdaba9..2eb23a9f3aba5e088ecea60b09cab831dc42e982 100644
--- a/core/modules/system/tests/http.php
+++ b/core/modules/system/tests/http.php
@@ -16,8 +16,9 @@
 $_SERVER['HTTPS'] = NULL;
 ini_set('session.cookie_secure', FALSE);
 foreach ($_SERVER as &$value) {
-  $value = str_replace('core/modules/system/tests/http.php', 'index.php', $value);
-  $value = str_replace('https://', 'http://', $value);
+  // Because HTTPS is null.
+  $value = $value ? str_replace('core/modules/system/tests/http.php', 'index.php', $value) : "";
+  $value = $value ? str_replace('https://', 'http://', $value) : "";
 }
 
 $kernel = new TestKernel('testing', $autoloader, TRUE);
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index 93787ff51b3abfe1797d827f84468706d31660bb..ecf0c50240a4aa92932f6f87af4861fd43efb952 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -251,9 +251,9 @@ function common_test_page_attachments_alter(array &$page) {
  */
 function common_test_js_alter(&$javascript, AttachedAssetsInterface $assets, LanguageInterface $language) {
   // Attach alter.js above tableselect.js.
-  $alterjs = \Drupal::service('extension.list.module')->getPath('common_test') . '/alter.js';
-  if (array_key_exists($alterjs, $javascript) && array_key_exists('core/misc/tableselect.js', $javascript)) {
-    $javascript[$alterjs]['weight'] = $javascript['core/misc/tableselect.js']['weight'] - 1;
+  $alter_js = \Drupal::service('extension.list.module')->getPath('common_test') . '/alter.js';
+  if (array_key_exists($alter_js, $javascript) && array_key_exists('core/misc/tableselect.js', $javascript)) {
+    $javascript[$alter_js]['weight'] = $javascript['core/misc/tableselect.js']['weight'] - 1;
   }
 }
 
diff --git a/core/modules/system/tests/modules/deprecation_test/deprecation_test.module b/core/modules/system/tests/modules/deprecation_test/deprecation_test.module
index 95aaa69b80a317c57d77677e58d5262c4e074dbb..94fe44ab1b1dda9e704aef2f2242a5d01801c9f8 100644
--- a/core/modules/system/tests/modules/deprecation_test/deprecation_test.module
+++ b/core/modules/system/tests/modules/deprecation_test/deprecation_test.module
@@ -17,6 +17,7 @@
  * @see https://www.drupal.org/project/drupal/issues/2870194
  */
 function deprecation_test_function() {
+  // phpcs:ignore Drupal.Semantics.FunctionTriggerError
   @trigger_error('This is the deprecation message for deprecation_test_function().', E_USER_DEPRECATED);
   return 'known_return_value';
 }
diff --git a/core/modules/system/tests/modules/deprecation_test/src/Deprecation/DrupalStandardsListenerDeprecatedClass.php b/core/modules/system/tests/modules/deprecation_test/src/Deprecation/DrupalStandardsListenerDeprecatedClass.php
index de4becba6837692c8635f911c9958487ea1e6e90..71a25055132455a8d088103eb3f0af57287194ae 100644
--- a/core/modules/system/tests/modules/deprecation_test/src/Deprecation/DrupalStandardsListenerDeprecatedClass.php
+++ b/core/modules/system/tests/modules/deprecation_test/src/Deprecation/DrupalStandardsListenerDeprecatedClass.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\deprecation_test\Deprecation;
 
+// phpcs:ignore Drupal.Semantics.FunctionTriggerError
 @trigger_error(__NAMESPACE__ . '\DrupalStandardsListenerDeprecatedClass is deprecated.', E_USER_DEPRECATED);
 
 /**
diff --git a/core/modules/system/tests/modules/deprecation_test/src/Deprecation/FixtureDeprecatedClass.php b/core/modules/system/tests/modules/deprecation_test/src/Deprecation/FixtureDeprecatedClass.php
index 65aea782506b1228a253603a71a8532a2cd1fef0..62e878bed601099d42d0788edae8c33c032e3793 100644
--- a/core/modules/system/tests/modules/deprecation_test/src/Deprecation/FixtureDeprecatedClass.php
+++ b/core/modules/system/tests/modules/deprecation_test/src/Deprecation/FixtureDeprecatedClass.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\deprecation_test\Deprecation;
 
+// phpcs:ignore Drupal.Semantics.FunctionTriggerError
 @trigger_error(__NAMESPACE__ . '\FixtureDeprecatedClass is deprecated.', E_USER_DEPRECATED);
 
 /**
diff --git a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml
index eedb8518312ab83b87cbdf25fc49a84585b50b63..39d5a0f812529aa191e264e63c7724f4f46a9d49 100644
--- a/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml
+++ b/core/modules/system/tests/modules/entity_reference_test_views/test_views/views.view.test_entity_reference_group_by_empty_relationships.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_entity_reference_group_by_empty_relationships
-label: ''
+label: test_entity_reference_group_by_empty_relationships
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestExternal.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestExternal.php
index b6587a9a9e151ffb41a84d3575054015ec8cc62f..0638f43da464d868a5911fd058564ea316d7d8a4 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestExternal.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestExternal.php
@@ -26,7 +26,7 @@ class EntityTestExternal extends EntityTest {
   /**
    * {@inheritdoc}
    */
-  public function toUrl($rel = 'canonical', array $options = []) {
+  public function toUrl($rel = NULL, array $options = []) {
     if ($rel === 'canonical') {
       return Url::fromUri('http://example.com', $options);
     }
diff --git a/core/modules/system/tests/modules/entity_test_revlog/src/Entity/EntityTestMulWithRevisionLog.php b/core/modules/system/tests/modules/entity_test_revlog/src/Entity/EntityTestMulWithRevisionLog.php
index 5a17b858dc1f0bfc444d52b87617acf5a49e8181..580aa691a3b814d237bb93c5447d24c6b8a9c1c1 100644
--- a/core/modules/system/tests/modules/entity_test_revlog/src/Entity/EntityTestMulWithRevisionLog.php
+++ b/core/modules/system/tests/modules/entity_test_revlog/src/Entity/EntityTestMulWithRevisionLog.php
@@ -8,6 +8,18 @@
  * @ContentEntityType(
  *   id = "entity_test_mul_revlog",
  *   label = @Translation("Test entity - data table, revisions log"),
+ *   handlers = {
+ *     "access" = \Drupal\entity_test_revlog\EntityTestRevlogAccessControlHandler::class,
+ *     "form" = {
+ *       "default" = \Drupal\Core\Entity\ContentEntityForm::class,
+ *       "revision-delete" = \Drupal\Core\Entity\Form\RevisionDeleteForm::class,
+ *       "revision-revert" = \Drupal\Core\Entity\Form\RevisionRevertForm::class,
+ *     },
+ *     "route_provider" = {
+ *       "html" = \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider::class,
+ *       "revision" = \Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider::class,
+ *     },
+ *   },
  *   base_table = "entity_test_mul_revlog",
  *   data_table = "entity_test_mul_revlog_field_data",
  *   revision_table = "entity_test_mul_revlog_revision",
@@ -26,6 +38,16 @@
  *     "revision_created" = "revision_created",
  *     "revision_log_message" = "revision_log_message"
  *   },
+ *   links = {
+ *     "add-form" = "/entity_test_mul_revlog/add",
+ *     "canonical" = "/entity_test_mul_revlog/manage/{entity_test_mul_revlog}",
+ *     "delete-form" = "/entity_test/delete/entity_test_mul_revlog/{entity_test_mul_revlog}",
+ *     "edit-form" = "/entity_test_mul_revlog/manage/{entity_test_mul_revlog}/edit",
+ *     "revision" = "/entity_test_mul_revlog/{entity_test_mul_revlog}/revision/{entity_test_mul_revlog_revision}/view",
+ *     "revision-delete-form" = "/entity_test_mul_revlog/{entity_test_mul_revlog}/revision/{entity_test_mul_revlog_revision}/delete",
+ *     "revision-revert-form" = "/entity_test_mul_revlog/{entity_test_mul_revlog}/revision/{entity_test_mul_revlog_revision}/revert",
+ *     "version-history" = "/entity_test_mul_revlog/{entity_test_mul_revlog}/revisions",
+ *   }
  * )
  */
 class EntityTestMulWithRevisionLog extends EntityTestWithRevisionLog {
diff --git a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.module b/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.module
index a2321aaf793a0429e3685525d3747349f5e2ad3f..3b7f5f4b052a5ebd22a2e3684e111a75a0136772 100644
--- a/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.module
+++ b/core/modules/system/tests/modules/experimental_module_requirements_test/experimental_module_requirements_test.module
@@ -11,8 +11,7 @@
 function experimental_module_requirements_test_help($route_name) {
   switch ($route_name) {
     case 'help.page.experimental_module_requirements_test':
-      // Make the help text conform to core standards. See
-      // \Drupal\system\Tests\Module\InstallUninstallTest::assertHelp().
+      // Make the help text conform to core standards.
       return t('The Experimental Requirements Test module is not done yet. It may eat your data, but you can read the <a href=":url">online documentation for the Experimental Requirements Test module</a>.', [':url' => 'http://www.example.com']);
   }
 }
diff --git a/core/modules/system/tests/modules/experimental_module_test/experimental_module_test.module b/core/modules/system/tests/modules/experimental_module_test/experimental_module_test.module
index 1f5072085f586f2f1607fde8dab86742dc75d5ad..937f1f033e69a19fa9ab43fd5c166c409a39f60b 100644
--- a/core/modules/system/tests/modules/experimental_module_test/experimental_module_test.module
+++ b/core/modules/system/tests/modules/experimental_module_test/experimental_module_test.module
@@ -14,8 +14,7 @@ function experimental_module_test_help($route_name, RouteMatchInterface $route_m
 
   switch ($route_name) {
     case 'help.page.experimental_module_test':
-      // Make the help text conform to core standards. See
-      // \Drupal\system\Tests\Module\InstallUninstallTest::assertHelp().
+      // Make the help text conform to core standards.
       return t('The Experimental Test module is not done yet. It may eat your data, but you can read the <a href=":url">online documentation for the Experimental Test module</a>.', [':url' => 'http://www.example.com']);
   }
 
diff --git a/core/modules/system/tests/modules/form_test/form_test.services.yml b/core/modules/system/tests/modules/form_test/form_test.services.yml
index 24513a66e0e6384ab9139129bbe1be2f1f9713cc..e6dfe4a62a795100fdad2f2ed21b9b6c6c6300a1 100644
--- a/core/modules/system/tests/modules/form_test/form_test.services.yml
+++ b/core/modules/system/tests/modules/form_test/form_test.services.yml
@@ -1,7 +1,7 @@
 services:
   form_test.form.serviceform:
     class: Drupal\form_test\FormTestServiceObject
-    arguments: ['@config.factory']
+    arguments: ['@config.factory', '@config.typed']
   form_test.event_subscriber:
     class: Drupal\form_test\EventSubscriber\FormTestEventSubscriber
     tags:
diff --git a/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php b/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php
index 428e9cef35f0216fb1327668dda703dcffb475ff..8e28437a42662da7c52e2d2ad02ecb53485793b5 100644
--- a/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php
+++ b/core/modules/system/tests/modules/form_test/src/FormTestControllerObject.php
@@ -34,7 +34,8 @@ protected function getEditableConfigNames() {
   public static function create(ContainerInterface $container) {
     \Drupal::messenger()->addStatus(t('The FormTestControllerObject::create() method was used for this form.'));
     return new static(
-      $container->get('config.factory')
+      $container->get('config.factory'),
+      $container->get('config.typed')
     );
   }
 
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml b/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml
index 9ba1b8f1bf4f9863c78237a7b2f3434719ca6c92..d694e419b85aca6e7693d7dadf070d553d75c8d2 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.links.menu.yml
@@ -15,6 +15,81 @@ menu_test.menu_callback_description.description-plain:
   description: 'Menu item description text'
   route_name: menu_test.callback_description_plain
   parent: menu_test.menu_callback_description
+menu_test.parent_test:
+  title: 'Menu Parent'
+  description: 'Menu item description parent'
+  route_name: menu_test.parent_test
+  parent: system.admin
+menu_test.parent_test.child1_test:
+  title: 'Menu child1'
+  description: 'Menu item description child1'
+  route_name: menu_test.child1_test
+  parent: menu_test.parent_test
+menu_test.parent_test.child2_test:
+  title: 'Menu child2'
+  description: 'Menu item description child2'
+  route_name: menu_test.child2_test
+  parent: menu_test.parent_test
+menu_test.parent_test.child3_test:
+  title: 'Menu child3'
+  description: 'Menu item description child3'
+  route_name: menu_test.child3_test
+  parent: menu_test.parent_test
+menu_test.parent_test.child_test.super_child1_test:
+  title: 'Menu super child1'
+  description: 'Menu item description super child1'
+  route_name: menu_test.super_child1_test
+  parent: menu_test.parent_test.child1_test
+menu_test.parent_test.child_test.super_child2_test:
+  title: 'Menu super child2'
+  description: 'Menu item description super child2'
+  route_name: menu_test.super_child2_test
+  parent: menu_test.parent_test.child2_test
+menu_test.parent_test.child_test.super_child3_test:
+  title: 'Menu super child3'
+  description: 'Menu item description super child3'
+  route_name: menu_test.super_child3_test
+  parent: menu_test.parent_test.child2_test
+menu_test.menu_parent_test_param:
+  title: 'Menu Parent Param'
+  description: 'Menu item description parent'
+  route_name: menu_test.parent_test_param
+  parent: system.admin
+  route_parameters:
+    param: 'param-in-menu'
+menu_test.menu_parent_test.child_test_param:
+  title: 'Menu Child Param'
+  description: 'Menu item description child'
+  route_name: menu_test.child_test_param
+  parent: menu_test.menu_parent_test_param
+  route_parameters:
+    param: 'param-in-menu'
+menu_test.menu_parent_test_param_default:
+  title: 'Menu Parent Param Default'
+  description: 'Menu item description parent'
+  route_name: menu_test.parent_test_param
+  parent: system.admin
+  route_parameters:
+    param: 'child_uses_default'
+menu_test.menu_parent_test.child_test_param_default:
+  title: 'Menu Child Param Default'
+  description: 'Menu item description child'
+  route_name: menu_test.child_test_param
+  parent: menu_test.menu_parent_test_param_default
+menu_test.menu_parent_test_param_default_explicit:
+  title: 'Menu Parent Param Default Explicit'
+  description: 'Menu item description parent'
+  route_name: menu_test.parent_test_param_explicit
+  parent: system.admin
+  route_parameters:
+    param: 'my_default'
+menu_test.menu_parent_test.child_test_param_default_explicit:
+  title: 'Menu Child Param Default Explicit'
+  description: 'Menu item description child'
+  route_name: menu_test.child_test_param_explicit
+  parent: menu_test.menu_parent_test_param_default_explicit
+  route_parameters:
+    param: 'my_default'
 menu_test.menu_no_title_callback:
   title: 'A title with @placeholder'
   route_name: menu_test.menu_no_title_callback
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.permissions.yml b/core/modules/system/tests/modules/menu_test/menu_test.permissions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..47c528f01254e9ad05d751bc07039884e0bc1da1
--- /dev/null
+++ b/core/modules/system/tests/modules/menu_test/menu_test.permissions.yml
@@ -0,0 +1,18 @@
+access parent test page:
+  title: 'Access parent test page'
+  restrict access: true
+access child1 test page:
+  title: 'Access child1 test page'
+  restrict access: true
+access child2 test page:
+  title: 'Access child2 test page'
+  restrict access: true
+access super child1 test page:
+  title: 'Access super child1 test page'
+  restrict access: true
+access super child2 test page:
+  title: 'Access super child2 test page'
+  restrict access: true
+access super child3 test page:
+  title: 'Access super child3 test page'
+  restrict access: true
diff --git a/core/modules/system/tests/modules/menu_test/menu_test.routing.yml b/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
index 59086d156cbdc4f0dbfd002e619ca04bb1792532..5b83aeb2af65962d60f158962395c1c38a25138e 100644
--- a/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
+++ b/core/modules/system/tests/modules/menu_test/menu_test.routing.yml
@@ -38,6 +38,86 @@ menu_test.callback_description_plain:
   requirements:
     _access: 'TRUE'
 
+menu_test.parent_test:
+  path: '/parent_test'
+  defaults:
+    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+  requirements:
+    _permission: 'access parent test page'
+
+menu_test.child1_test:
+  path: '/parent_test/child1_test'
+  defaults:
+    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+  requirements:
+    _permission: 'access child1 test page'
+
+menu_test.child2_test:
+  path: '/parent_test/child2_test'
+  defaults:
+    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+  requirements:
+    _permission: 'access child2 test page'
+
+menu_test.child3_test:
+  path: '/parent_test/child3_test'
+  defaults:
+    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+  requirements:
+    _access: 'TRUE'
+
+menu_test.super_child1_test:
+  path: '/parent_test/child_test/super_child1_test'
+  defaults:
+    _controller: '\Drupal\test_page_test\Controller\TestPageTestController::testPage'
+  requirements:
+    _permission: 'access super child1 test page'
+menu_test.super_child2_test:
+  path: '/parent_test/child_test/super_child2_test'
+  defaults:
+    _controller: '\Drupal\test_page_test\Controller\TestPageTestController::testPage'
+  requirements:
+    _permission: 'access super child2 test page'
+
+menu_test.super_child3_test:
+  path: '/parent_test/child_test/super_child3_test'
+  defaults:
+    _controller: '\Drupal\test_page_test\Controller\TestPageTestController::testPage'
+  requirements:
+    _permission: 'access super child3 test page'
+
+menu_test.parent_test_param:
+  path: '/parent_test_param/{param}'
+  defaults:
+    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    param: 'child_uses_default'
+  requirements:
+    _permission: 'access parent test page'
+
+menu_test.child_test_param:
+  path: '/parent_test_child_test_param/{param}'
+  defaults:
+    _controller: '\Drupal\test_page_test\Controller\TestPageTestController::testPage'
+    param: 'my_default'
+  requirements:
+    _permission: 'access child1 test page'
+
+menu_test.parent_test_param_explicit:
+  path: '/parent_test_param_explicit/{param}'
+  defaults:
+    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
+    param: 'my_default'
+  requirements:
+    _permission: 'access parent test page'
+
+menu_test.child_test_param_explicit:
+  path: '/parent_test_child_test_param_explicit/{param}'
+  defaults:
+    _controller: '\Drupal\test_page_test\Controller\TestPageTestController::testPage'
+    param: 'my_default'
+  requirements:
+    _permission: 'access child1 test page'
+
 menu_test.menu_no_title_callback:
   path: '/menu_no_title_callback'
   defaults:
diff --git a/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
index 19e0bffe29b5ac168a4d7bbe8ee6f86fd7850b0a..8f8fa74bb1864b895b28436dcf3685e1baae5197 100644
--- a/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
+++ b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
@@ -226,7 +226,9 @@ public function renderPageWithoutDuplicateIds() {
   public function deprecations() {
     // Create 2 identical deprecation messages. This should only trigger a
     // single response header.
+    // phpcs:ignore Drupal.Semantics.FunctionTriggerError
     @trigger_error('Test deprecation message', E_USER_DEPRECATED);
+    // phpcs:ignore Drupal.Semantics.FunctionTriggerError
     @trigger_error('Test deprecation message', E_USER_DEPRECATED);
     return [
       '#markup' => 'Content that triggers deprecation messages',
diff --git a/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php b/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php
index 4b2e38e84d52e3fd428deb5e9948184c495033a6..b2e340583e76c1f595f172ee63acad64459e1b67 100644
--- a/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php
+++ b/core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php
@@ -625,7 +625,7 @@ public function testReferencedEntity() {
    * @see https://www.drupal.org/node/3354596
    */
   protected function createCacheId(array $keys, array $contexts) {
-    @trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is no replacement. See: https://www.drupal.org/project/drupal/issues/2551419.', E_USER_DEPRECATED);
+    @trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3354596', E_USER_DEPRECATED);
     $cid_parts = $keys;
 
     $contexts = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($contexts);
diff --git a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
index 9ddd26d9bc25f792ffff4dd46a3394e4f1e41c11..5f0ee86072e9be557a1a707c2b8026c05792dfb6 100644
--- a/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
+++ b/core/modules/system/tests/src/Functional/FileTransfer/FileTransferTest.php
@@ -79,23 +79,23 @@ public function testJail() {
 
     // This convoluted piece of code is here because our testing framework does
     // not support expecting exceptions.
-    $gotit = FALSE;
+    $got_it = FALSE;
     try {
       $this->testConnection->copyDirectory($source, sys_get_temp_dir());
     }
     catch (FileTransferException $e) {
-      $gotit = TRUE;
+      $got_it = TRUE;
     }
-    $this->assertTrue($gotit, 'Was not able to copy a directory outside of the jailed area.');
+    $this->assertTrue($got_it, 'Was not able to copy a directory outside of the jailed area.');
 
-    $gotit = TRUE;
+    $got_it = TRUE;
     try {
       $this->testConnection->copyDirectory($source, $this->root . '/' . PublicStream::basePath());
     }
     catch (FileTransferException $e) {
-      $gotit = FALSE;
+      $got_it = FALSE;
     }
-    $this->assertTrue($gotit, 'Was able to copy a directory inside of the jailed area');
+    $this->assertTrue($got_it, 'Was able to copy a directory inside of the jailed area');
   }
 
 }
diff --git a/core/modules/system/tests/src/Functional/Form/ElementTest.php b/core/modules/system/tests/src/Functional/Form/ElementTest.php
index 3513d7deb5536e88f0173515c95cdd847501b0e8..17e48370509e0ad793b16b0edac3f5826bc5a8c7 100644
--- a/core/modules/system/tests/src/Functional/Form/ElementTest.php
+++ b/core/modules/system/tests/src/Functional/Form/ElementTest.php
@@ -8,6 +8,7 @@
  * Tests building and processing of core form elements.
  *
  * @group Form
+ * @group #slow
  */
 class ElementTest extends BrowserTestBase {
 
diff --git a/core/modules/system/tests/src/Functional/Form/FormTest.php b/core/modules/system/tests/src/Functional/Form/FormTest.php
index 3797cbb137769ea703fa23b8cd81df8e63d07854..21a48311cfd8e7f03a78cbe9ad4828d9e485bb69 100644
--- a/core/modules/system/tests/src/Functional/Form/FormTest.php
+++ b/core/modules/system/tests/src/Functional/Form/FormTest.php
@@ -18,6 +18,7 @@
  * Tests various form element validation mechanisms.
  *
  * @group Form
+ * @group #slow
  */
 class FormTest extends BrowserTestBase {
 
diff --git a/core/modules/system/tests/src/Functional/GenericTest.php b/core/modules/system/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5311043d9f5e858e3c4d9903c6fecf49c3d1df34
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\system\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for system.
+ *
+ * @group system
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php b/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php
index ac85028b51d0719a1b92dea212258281dd998756..307fbf3613002db5dd45fe43ebc8ea40735ffc52 100644
--- a/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php
+++ b/core/modules/system/tests/src/Functional/Menu/MenuAccessTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\system\Functional\Menu;
 
+use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
 use Drupal\Tests\BrowserTestBase;
 
@@ -17,7 +18,7 @@ class MenuAccessTest extends BrowserTestBase {
    *
    * @var array
    */
-  protected static $modules = ['block', 'menu_test'];
+  protected static $modules = ['block', 'filter', 'menu_test', 'toolbar'];
 
   /**
    * {@inheritdoc}
@@ -71,4 +72,242 @@ public function testMenuBlockLinksAccessCheck() {
     $this->assertSession()->linkByHrefNotExists('foo/asdf/c');
   }
 
+  /**
+   * Test routes implementing _access_admin_menu_block_page.
+   *
+   * @covers \Drupal\system\EventSubscriber\AccessRouteAlterSubscriber::accessAdminMenuBlockPage
+   * @covers \Drupal\system\Access\SystemAdminMenuBlockAccessCheck::access
+   */
+  public function testSystemAdminMenuBlockAccessCheck(): void {
+    // Create an admin user.
+    $adminUser = $this->drupalCreateUser([], NULL, TRUE);
+
+    // Create a user with 'administer menu' permission.
+    $menuAdmin = $this->drupalCreateUser([
+      'access administration pages',
+      'administer menu',
+    ]);
+
+    // Create a user with 'administer filters' permission.
+    $filterAdmin = $this->drupalCreateUser([
+      'access administration pages',
+      'administer filters',
+    ]);
+
+    // Create a user with 'access administration pages' permission.
+    $webUser = $this->drupalCreateUser([
+      'access administration pages',
+    ]);
+
+    // An admin user has access to all parent pages.
+    $this->drupalLogin($adminUser);
+    $this->assertMenuItemRoutesAccess(200, 'admin/structure', 'admin/people');
+
+    // This user has access to administer menus so the structure parent page
+    // should be accessible.
+    $this->drupalLogin($menuAdmin);
+    $this->assertMenuItemRoutesAccess(200, 'admin/structure');
+    $this->assertMenuItemRoutesAccess(403, 'admin/people');
+
+    // This user has access to administer filters so the config parent page
+    // should be accessible.
+    $this->drupalLogin($filterAdmin);
+    $this->assertMenuItemRoutesAccess(200, 'admin/config');
+    $this->assertMenuItemRoutesAccess(403, 'admin/people');
+
+    // This user doesn't have access to any of the child pages, so the parent
+    // pages should not be accessible.
+    $this->drupalLogin($webUser);
+    $this->assertMenuItemRoutesAccess(403, 'admin/structure', 'admin/people');
+    // As menu_test adds a menu link under config.
+    $this->assertMenuItemRoutesAccess(200, 'admin/config');
+
+    // Test access to routes in the admin menu. The routes are in a menu tree
+    // of the hierarchy:
+    // menu_test.parent_test
+    // -menu_test.child1_test
+    // --menu_test.super_child1_test
+    // -menu_test.child2_test
+    // --menu_test.super_child2_test
+    // --menu_test.super_child3_test
+    // -menu_test.child3_test
+    // All routes in this tree except the "super_child" routes should have the
+    // '_access_admin_menu_block_page' requirement which denies access unless
+    // the user has access to a menu item under that route. Route
+    // 'menu_test.child3_test' has no menu items underneath it so no user should
+    // have access to this route even though it has the requirement
+    // `_access: 'TRUE'`.
+    $tree_routes = [
+      'menu_test.parent_test',
+      'menu_test.child1_test',
+      'menu_test.child2_test',
+      'menu_test.child3_test',
+      'menu_test.super_child1_test',
+      'menu_test.super_child2_test',
+      'menu_test.super_child3_test',
+    ];
+
+    // Create a user with access to only the top level parent.
+    $parentUser = $this->drupalCreateUser([
+      'access parent test page',
+    ]);
+    // Create a user with access to the parent and child routes but none of the
+    // super child routes.
+    $childOnlyUser = $this->drupalCreateUser([
+      'access parent test page',
+      'access child1 test page',
+      'access child2 test page',
+    ]);
+    // Create 3 users all with access the parent and child but only 1 super
+    // child route.
+    $superChild1User = $this->drupalCreateUser([
+      'access parent test page',
+      'access child1 test page',
+      'access child2 test page',
+      'access super child1 test page',
+    ]);
+    $superChild2User = $this->drupalCreateUser([
+      'access parent test page',
+      'access child1 test page',
+      'access child2 test page',
+      'access super child2 test page',
+    ]);
+    $superChild3User = $this->drupalCreateUser([
+      'access parent test page',
+      'access child1 test page',
+      'access child2 test page',
+      'access super child3 test page',
+    ]);
+    $noParentAccessUser = $this->drupalCreateUser([
+      'access child1 test page',
+      'access child2 test page',
+      'access super child1 test page',
+      'access super child2 test page',
+      'access super child3 test page',
+    ]);
+
+    // Users that do not have access to any of the 'super_child' routes will
+    // not have access to any of the routes in the tree.
+    $this->assertUserRoutesAccess($parentUser, [], ...$tree_routes);
+    $this->assertUserRoutesAccess($childOnlyUser, [], ...$tree_routes);
+
+    // A user that does not have access to the top level parent but has access
+    // to all the other routes will have access to all routes except the parent
+    // and 'menu_test.child3_test', because it has no items underneath in the
+    // menu.
+    $this->assertUserRoutesAccess(
+      $noParentAccessUser,
+      array_diff($tree_routes, ['menu_test.parent_test', 'menu_test.child3_test']),
+      ...$tree_routes
+    );
+    // Users who have only access to one super child route should have access
+    // only to that route and its parents.
+    $this->assertUserRoutesAccess(
+      $superChild1User,
+      ['menu_test.parent_test', 'menu_test.child1_test', 'menu_test.super_child1_test'],
+      ...$tree_routes);
+    $this->assertUserRoutesAccess(
+      $superChild2User,
+      ['menu_test.parent_test', 'menu_test.child2_test', 'menu_test.super_child2_test'],
+      ...$tree_routes);
+    $this->assertUserRoutesAccess(
+      $superChild3User,
+      // The 'menu_test.super_child3_test' menu item is nested under
+      // 'menu_test.child2_test' to ensure access is correct when there are
+      // multiple items nested at the same level.
+      ['menu_test.parent_test', 'menu_test.child2_test', 'menu_test.super_child3_test'],
+      ...$tree_routes);
+
+    // Test a route that has parameter defined in the menu item.
+    $this->drupalLogin($parentUser);
+    $this->assertMenuItemRoutesAccess(403, Url::fromRoute('menu_test.parent_test_param', ['param' => 'param-in-menu']));
+    $this->drupalLogin($childOnlyUser);
+    $this->assertMenuItemRoutesAccess(200, Url::fromRoute('menu_test.parent_test_param', ['param' => 'param-in-menu']));
+
+    // Test a route that does not have a parameter defined in the menu item but
+    // uses the route default parameter.
+    // @todo Change the following test case to use a parent menu item that also
+    //   uses the routes default parameter in https://drupal.org/i/3359511.
+    $this->drupalLogin($parentUser);
+    $this->assertMenuItemRoutesAccess(
+      403,
+      Url::fromRoute('menu_test.parent_test_param', ['param' => 'child_uses_default']),
+      Url::fromRoute('menu_test.child_test_param', ['param' => 'child_uses_default']),
+
+    );
+    $this->drupalLogin($childOnlyUser);
+    $this->assertMenuItemRoutesAccess(
+      200,
+      Url::fromRoute('menu_test.parent_test_param', ['param' => 'child_uses_default']),
+      Url::fromRoute('menu_test.child_test_param', ['param' => 'child_uses_default']),
+    );
+
+    // Test a route that does have a parameter defined in the menu item and that
+    // parameter value is equal to the default value specific in the route.
+    $this->drupalLogin($parentUser);
+    $this->assertMenuItemRoutesAccess(
+      403,
+      Url::fromRoute('menu_test.parent_test_param_explicit', ['param' => 'my_default']),
+      Url::fromRoute('menu_test.child_test_param_explicit', ['param' => 'my_default'])
+    );
+    $this->drupalLogin($childOnlyUser);
+    $this->assertMenuItemRoutesAccess(
+      200,
+      Url::fromRoute('menu_test.parent_test_param_explicit', ['param' => 'my_default']),
+      Url::fromRoute('menu_test.child_test_param_explicit', ['param' => 'my_default'])
+    );
+
+    // If we try to access a route that takes a parameter but route is not in the
+    // with that parameter we should always be denied access because the sole
+    // purpose of \Drupal\system\Controller\SystemController::systemAdminMenuBlockPage
+    // is to display items in the menu.
+    $this->drupalLogin($parentUser);
+    $this->assertMenuItemRoutesAccess(
+      403,
+      Url::fromRoute('menu_test.parent_test_param', ['param' => 'any-other']),
+      // $parentUser does not have the 'access child1 test page' permission.
+      Url::fromRoute('menu_test.child_test_param', ['param' => 'any-other'])
+    );
+    $this->drupalLogin($childOnlyUser);
+    $this->assertMenuItemRoutesAccess(403, Url::fromRoute('menu_test.parent_test_param', ['param' => 'any-other']));
+    // $childOnlyUser has the 'access child1 test page' permission.
+    $this->assertMenuItemRoutesAccess(200, Url::fromRoute('menu_test.child_test_param', ['param' => 'any-other']));
+  }
+
+  /**
+   * Asserts route requests connected to menu items have the expected access.
+   *
+   * @param int $expected_status
+   *   The expected request status.
+   * @param string|\Drupal\Core\Url ...$paths
+   *   The paths as passed to \Drupal\Tests\UiHelperTrait::drupalGet().
+   */
+  private function assertMenuItemRoutesAccess(int $expected_status, string|Url ...$paths): void {
+    foreach ($paths as $path) {
+      $this->drupalGet($path);
+      $this->assertSession()->statusCodeEquals($expected_status);
+      $this->assertSession()->pageTextNotContains('You do not have any administrative items.');
+    }
+  }
+
+  /**
+   * Asserts which routes a user has access to.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $user
+   *   The user account for which to check access.
+   * @param array $accessibleRoutes
+   *   The routes the user should have access to.
+   * @param string ...$allRoutes
+   *   The routes to check.
+   */
+  private function assertUserRoutesAccess(AccountInterface $user, array $accessibleRoutes, string ...$allRoutes): void {
+    $this->drupalLogin($user);
+    foreach ($allRoutes as $route) {
+      $this->assertMenuItemRoutesAccess(
+        in_array($route, $accessibleRoutes, TRUE) ? 200 : 403,
+        Url::fromRoute($route)
+      );
+    }
+  }
+
 }
diff --git a/core/modules/system/tests/src/Functional/Module/DependencyTest.php b/core/modules/system/tests/src/Functional/Module/DependencyTest.php
index 23e49e9441bc92db890fcfc1c3adc6802b3d89c4..aa0cd9bdf840203552618287cff8929198ec37a3 100644
--- a/core/modules/system/tests/src/Functional/Module/DependencyTest.php
+++ b/core/modules/system/tests/src/Functional/Module/DependencyTest.php
@@ -9,6 +9,7 @@
  * Enable module without dependency enabled.
  *
  * @group Module
+ * @group #slow
  */
 class DependencyTest extends ModuleTestBase {
 
diff --git a/core/modules/system/tests/src/Functional/Module/GenericModuleTestBase.php b/core/modules/system/tests/src/Functional/Module/GenericModuleTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa3d81e7a4f5707f0200ce9fa2e400ef1e1f4751
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Module/GenericModuleTestBase.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Module;
+
+use Drupal\Core\Database\Database;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Runs a series of generic tests for one module.
+ */
+abstract class GenericModuleTestBase extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'help',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Get the module name.
+   *
+   * @return string
+   *   The module to test.
+   */
+  protected function getModule(): string {
+    return explode('\\', get_class($this))[2];
+  }
+
+  /**
+   * Checks some generic things about a module.
+   */
+  public function testModuleGenericIssues(): void {
+    $module = $this->getModule();
+    \Drupal::service('module_installer')->install([$module]);
+    $info = \Drupal::service('extension.list.module')->getExtensionInfo($module);
+    if (!empty($info['required']) && !empty($info['hidden'])) {
+      $this->markTestSkipped('Nothing to assert for hidden, required modules.');
+    }
+    $this->drupalLogin($this->rootUser);
+    $this->assertHookHelp($module);
+
+    if (empty($info['required'])) {
+      $connection = Database::getConnection();
+
+      // When the database driver is provided by a module, then that module
+      // cannot be uninstalled.
+      if ($module !== $connection->getProvider()) {
+        // Check that the module can be uninstalled and then re-installed again.
+        $this->preUnInstallSteps();
+        $this->assertTrue(\Drupal::service('module_installer')->uninstall([$module]), "Failed to uninstall '$module' module");
+        $this->assertTrue(\Drupal::service('module_installer')->install([$module]), "Failed to install '$module' module");
+      }
+    }
+  }
+
+  /**
+   * Verifies hook_help() syntax.
+   *
+   * @param string $module
+   *   The module.
+   */
+  protected function assertHookHelp(string $module): void {
+    $info = \Drupal::service('extension.list.module')->getExtensionInfo($module);
+    if (empty($info['hidden'])) {
+      $this->drupalGet('admin/help/' . $module);
+      $this->assertSession()->statusCodeEquals(200);
+      $this->assertSession()->pageTextContains($info['name'] . ' module');
+      $this->assertSession()->linkExists('online documentation for the ' . $info['name'] . ' module', 0, "Correct online documentation link is in the help page for $module");
+    }
+  }
+
+  /**
+   * Helper to perform any steps required prior to uninstalling a module.
+   */
+  protected function preUnInstallSteps(): void {}
+
+}
diff --git a/core/modules/system/tests/src/Functional/Module/GenericTest.php b/core/modules/system/tests/src/Functional/Module/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3457048cd67cef67c8dc325a00f75242e47ef6e
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Module/GenericTest.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Module;
+
+/**
+ * Generic test for system module.
+ *
+ * @group system
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php b/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php
deleted file mode 100644
index 808235a9bf4999cbdbd40decdef91d7c761c39c1..0000000000000000000000000000000000000000
--- a/core/modules/system/tests/src/Functional/Module/InstallUninstallTest.php
+++ /dev/null
@@ -1,436 +0,0 @@
-<?php
-
-namespace Drupal\Tests\system\Functional\Module;
-
-use Drupal\Core\Extension\ExtensionLifecycle;
-use Drupal\Core\Logger\RfcLogLevel;
-use Drupal\workspaces\Entity\Workspace;
-
-/**
- * Install/uninstall core module and confirm table creation/deletion.
- *
- * @group #slow
- * @group Module
- */
-class InstallUninstallTest extends ModuleTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $defaultTheme = 'stark';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected static $modules = [
-    'system_test',
-    'dblog',
-    'taxonomy',
-    'update_test_postupdate',
-  ];
-
-  /**
-   * Tests that a fixed set of modules can be installed and uninstalled.
-   */
-  public function testInstallUninstall() {
-    // Set a variable so that the hook implementations in system_test.module
-    // will display messages via
-    // \Drupal\Core\Messenger\MessengerInterface::addStatus().
-    $this->container->get('state')->set('system_test.verbose_module_hooks', TRUE);
-
-    // Install and uninstall module_test to ensure hook_preinstall_module and
-    // hook_preuninstall_module are fired as expected.
-    $this->container->get('module_installer')->install(['module_test']);
-    $this->assertEquals('module_test', $this->container->get('state')->get('system_test_preinstall_module'));
-    $this->container->get('module_installer')->uninstall(['module_test']);
-    $this->assertEquals('module_test', $this->container->get('state')->get('system_test_preuninstall_module'));
-    $this->resetAll();
-
-    $all_modules = $this->container->get('extension.list.module')->getList();
-
-    // Test help on required modules, but do not test uninstalling.
-    $required_modules = array_filter($all_modules, function ($module) {
-      if (!empty($module->info['required']) || $module->status == TRUE) {
-        if ($module->info['package'] != 'Testing' && empty($module->info['hidden'])) {
-          return TRUE;
-        }
-      }
-      return FALSE;
-    });
-
-    $required_modules['help'] = $all_modules['help'];
-
-    // Filter out contrib, hidden, testing, experimental, and deprecated
-    // modules. We also don't need to enable modules that are already enabled.
-    $all_modules = array_filter($all_modules, function ($module) {
-      if (!empty($module->info['hidden'])
-        || !empty($module->info['required'])
-        || $module->status == TRUE
-        || $module->info['package'] === 'Testing'
-        || $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
-        return FALSE;
-      }
-      return TRUE;
-    });
-
-    // Install the Help module, and verify it installed successfully.
-    unset($all_modules['help']);
-    $this->assertModuleNotInstalled('help');
-    $edit = [];
-    $edit["modules[help][enable]"] = TRUE;
-    $this->drupalGet('admin/modules');
-    $this->submitForm($edit, 'Install');
-    $this->assertSession()->pageTextContains('has been enabled');
-    $this->assertSession()->pageTextContains('hook_modules_installed fired for help');
-    $this->assertModuleSuccessfullyInstalled('help');
-
-    // Add new role to allow browsing help pages.
-    $this->adminUser->addRole($this->createRole(['access help pages']))->save();
-
-    // Test help for the required modules.
-    foreach ($required_modules as $name => $module) {
-      $this->assertHelp($name, $module->info['name']);
-    }
-
-    // Go through each module in the list and try to install and uninstall
-    // it with its dependencies.
-    foreach ($all_modules as $name => $module) {
-      $was_installed_list = \Drupal::moduleHandler()->getModuleList();
-
-      // Start a list of modules that we expect to be installed this time.
-      $modules_to_install = [$name];
-      foreach (array_keys($module->requires) as $dependency) {
-        if (isset($all_modules[$dependency])) {
-          $modules_to_install[] = $dependency;
-        }
-      }
-
-      // Check that each module is not yet enabled and does not have any
-      // database tables yet.
-      foreach ($modules_to_install as $module_to_install) {
-        $this->assertModuleNotInstalled($module_to_install);
-      }
-
-      // Install the module.
-      $edit = [];
-      $lifecycle = $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER];
-      $edit['modules[' . $name . '][enable]'] = TRUE;
-      $this->drupalGet('admin/modules');
-      $this->submitForm($edit, 'Install');
-
-      // Handle experimental modules, which require a confirmation screen.
-      if ($lifecycle === ExtensionLifecycle::EXPERIMENTAL) {
-        $this->assertSession()->pageTextContains('Are you sure you wish to enable an experimental module?');
-        if (count($modules_to_install) > 1) {
-          // When there are experimental modules, needed dependencies do not
-          // result in the same page title, but there will be expected text
-          // indicating they need to be enabled.
-          $this->assertSession()->pageTextContains('You must enable');
-        }
-        $this->submitForm([], 'Continue');
-      }
-      // Handle deprecated modules, which require a confirmation screen.
-      elseif ($lifecycle === ExtensionLifecycle::DEPRECATED) {
-        $this->assertSession()->pageTextContains('Are you sure you wish to enable a deprecated module?');
-        if (count($modules_to_install) > 1) {
-          // When there are deprecated modules, needed dependencies do not
-          // result in the same page title, but there will be expected text
-          // indicating they need to be enabled.
-          $this->assertSession()->pageTextContains('You must enable');
-        }
-        $this->submitForm([], 'Continue');
-      }
-      // Handle the case where modules were installed along with this one and
-      // where we therefore hit a confirmation screen.
-      elseif (count($modules_to_install) > 1) {
-        // Verify that we are on the correct form and that the expected text
-        // about enabling dependencies appears.
-        $this->assertSession()->pageTextContains('Some required modules must be enabled');
-        $this->assertSession()->pageTextContains('You must enable');
-        $this->submitForm([], 'Continue');
-      }
-
-      // List the module display names to check the confirmation message.
-      $module_names = [];
-      foreach ($modules_to_install as $module_to_install) {
-        $module_names[] = $all_modules[$module_to_install]->info['name'];
-      }
-      if (count($modules_to_install) > 1) {
-        $this->assertSession()->pageTextContains(count($module_names) . ' modules have been enabled: ' . implode(', ', $module_names));
-      }
-      else {
-        $this->assertSession()->pageTextContains('Module ' . $module_names[0] . ' has been enabled.');
-      }
-
-      // Check that hook_modules_installed() was invoked with the expected list
-      // of modules, that each module's database tables now exist, and that
-      // appropriate messages appear in the logs.
-      foreach ($modules_to_install as $module_to_install) {
-        $this->assertSession()->pageTextContains('hook_modules_installed fired for ' . $module_to_install);
-        $this->assertLogMessage('system', "%module module installed.", ['%module' => $module_to_install], RfcLogLevel::INFO);
-        $this->assertInstallModuleUpdates($module_to_install);
-        $this->assertModuleSuccessfullyInstalled($module_to_install);
-      }
-
-      // Verify the help page.
-      $this->assertHelp($name, $module->info['name']);
-
-      // Uninstall the original module, plus everything else that was installed
-      // with it.
-      // @todo Remove in https://www.drupal.org/project/node/3261652
-      if ($name == 'forum') {
-        // Forum has an extra step to be able to uninstall it.
-        $this->preUninstallForum();
-      }
-
-      // Delete all workspaces before uninstall.
-      if ($name == 'workspaces') {
-        $workspaces = Workspace::loadMultiple();
-        \Drupal::entityTypeManager()->getStorage('workspace')->delete($workspaces);
-      }
-
-      $now_installed_list = \Drupal::moduleHandler()->getModuleList();
-      $added_modules = array_diff(array_keys($now_installed_list), array_keys($was_installed_list));
-      while ($added_modules) {
-        $initial_count = count($added_modules);
-        foreach ($added_modules as $to_uninstall) {
-          // See if we can currently uninstall this module (if its dependencies
-          // have been uninstalled), and do so if we can.
-          $this->drupalGet('admin/modules/uninstall');
-          $checkbox = $this->assertSession()->fieldExists("uninstall[$to_uninstall]");
-          if (!$checkbox->hasAttribute('disabled')) {
-            // This one is eligible for being uninstalled.
-            $package = $all_modules[$to_uninstall]->info['package'];
-            $this->assertSuccessfulUninstall($to_uninstall, $package);
-            $added_modules = array_diff($added_modules, [$to_uninstall]);
-          }
-        }
-
-        // If we were not able to find a module to uninstall, fail and exit the
-        // loop.
-        $final_count = count($added_modules);
-        if ($initial_count == $final_count) {
-          $this->fail('Remaining modules could not be uninstalled for ' . $name);
-          break;
-        }
-      }
-    }
-
-    // Uninstall the help module and put it back into the list of modules.
-    $all_modules['help'] = $required_modules['help'];
-    $this->assertSuccessfulUninstall('help', $required_modules['help']->info['package']);
-
-    // Now that all modules have been tested, go back and try to enable them
-    // all again at once. This tests two things:
-    // - That each module can be successfully enabled again after being
-    //   uninstalled.
-    // - That enabling more than one module at the same time does not lead to
-    //   any errors.
-    $edit = [];
-    $count_experimental = 0;
-    $count_deprecated = 0;
-    foreach ($all_modules as $name => $module) {
-      $edit['modules[' . $name . '][enable]'] = TRUE;
-      if ($module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::EXPERIMENTAL) {
-        $count_experimental++;
-      }
-      if ($module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
-        $count_deprecated++;
-      }
-    }
-    $this->drupalGet('admin/modules');
-    $this->submitForm($edit, 'Install');
-
-    // If there are experimental and deprecated modules, click the confirm form.
-    if ($count_experimental > 0 && $count_deprecated > 0) {
-      $this->assertSession()->titleEquals('Are you sure you wish to enable experimental and deprecated modules? | Drupal');
-      $this->submitForm([], 'Continue');
-    }
-    // If there are experimental, and no deprecated modules, click the confirm
-    // form.
-    elseif ($count_experimental > 0) {
-      if ($count_experimental === 1) {
-        $page_title = 'Are you sure you wish to enable an experimental module? | Drupal';
-      }
-      else {
-        $page_title = 'Are you sure you wish to enable experimental modules? | Drupal';
-      }
-      $this->assertSession()->titleEquals($page_title);
-      $this->submitForm([], 'Continue');
-    }
-    // If there are deprecated, and no experimental modules, click the confirm
-    // form.
-    elseif ($count_deprecated > 0) {
-      if ($count_deprecated === 1) {
-        $page_title = 'Are you sure you wish to enable a deprecated module? | Drupal';
-      }
-      else {
-        $page_title = 'Are you sure you wish to enable deprecated modules? | Drupal';
-      }
-      $this->assertSession()->titleEquals($page_title);
-      $this->submitForm([], 'Continue');
-    }
-    $this->assertSession()->pageTextContains(count($all_modules) . ' modules have been enabled: ');
-  }
-
-  /**
-   * Asserts that a module is not yet installed.
-   *
-   * @param string $name
-   *   Name of the module to check.
-   *
-   * @internal
-   */
-  protected function assertModuleNotInstalled(string $name): void {
-    $this->assertModules([$name], FALSE);
-    $this->assertModuleTablesDoNotExist($name);
-  }
-
-  /**
-   * Asserts that a module was successfully installed.
-   *
-   * @param string $name
-   *   Name of the module to check.
-   *
-   * @internal
-   */
-  protected function assertModuleSuccessfullyInstalled(string $name): void {
-    $this->assertModules([$name], TRUE);
-    $this->assertModuleTablesExist($name);
-    $this->assertModuleConfig($name);
-  }
-
-  /**
-   * Uninstalls a module and asserts that it was done correctly.
-   *
-   * @param string $module
-   *   The name of the module to uninstall.
-   * @param string $package
-   *   (optional) The package of the module to uninstall. Defaults
-   *   to 'Core'.
-   *
-   * @internal
-   */
-  protected function assertSuccessfulUninstall(string $module, string $package = 'Core'): void {
-    $edit = [];
-    $edit['uninstall[' . $module . ']'] = TRUE;
-    $this->drupalGet('admin/modules/uninstall');
-    $this->submitForm($edit, 'Uninstall');
-    $this->submitForm([], 'Uninstall');
-    $this->assertSession()->pageTextContains('The selected modules have been uninstalled.');
-    $this->assertModules([$module], FALSE);
-
-    // Check that the appropriate hook was fired and the appropriate log
-    // message appears. (But don't check for the log message if the dblog
-    // module was just uninstalled, since the {watchdog} table won't be there
-    // anymore.)
-    $this->assertSession()->pageTextContains('hook_modules_uninstalled fired for ' . $module);
-    $this->assertLogMessage('system', "%module module uninstalled.", ['%module' => $module], RfcLogLevel::INFO);
-
-    // Check that the module's database tables no longer exist.
-    $this->assertModuleTablesDoNotExist($module);
-    // Check that the module's config files no longer exist.
-    $this->assertNoModuleConfig($module);
-    $this->assertUninstallModuleUpdates($module);
-  }
-
-  /**
-   * Asserts the module post update functions after install.
-   *
-   * @param string $module
-   *   The module that got installed.
-   *
-   * @internal
-   */
-  protected function assertInstallModuleUpdates(string $module): void {
-    /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
-    $post_update_registry = \Drupal::service('update.post_update_registry');
-    $all_update_functions = $post_update_registry->getPendingUpdateFunctions();
-    $empty_result = TRUE;
-    foreach ($all_update_functions as $function) {
-      [$function_module] = explode('_post_update_', $function);
-      if ($module === $function_module) {
-        $empty_result = FALSE;
-        break;
-      }
-    }
-    $this->assertTrue($empty_result, 'Ensures that no pending post update functions are available.');
-
-    $existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []);
-    switch ($module) {
-      case 'update_test_postupdate':
-        $expected = [
-          'update_test_postupdate_post_update_first',
-          'update_test_postupdate_post_update_second',
-          'update_test_postupdate_post_update_test1',
-          'update_test_postupdate_post_update_test0',
-          'update_test_postupdate_post_update_foo',
-          'update_test_postupdate_post_update_bar',
-          'update_test_postupdate_post_update_baz',
-        ];
-        $this->assertSame($expected, $existing_updates);
-        break;
-    }
-  }
-
-  /**
-   * Asserts the module post update functions after uninstall.
-   *
-   * @param string $module
-   *   The module that got installed.
-   *
-   * @internal
-   */
-  protected function assertUninstallModuleUpdates(string $module): void {
-    /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
-    $post_update_registry = \Drupal::service('update.post_update_registry');
-    $all_update_functions = $post_update_registry->getPendingUpdateFunctions();
-
-    switch ($module) {
-      case 'update_test_postupdate':
-        $this->assertEmpty(array_intersect(['update_test_postupdate_post_update_first'], $all_update_functions), 'Asserts that no pending post update functions are available.');
-
-        $existing_updates = \Drupal::keyValue('post_update')->get('existing_updates', []);
-        $this->assertEmpty(array_intersect(['update_test_postupdate_post_update_first'], $existing_updates), 'Asserts that no post update functions are stored in keyvalue store.');
-        break;
-    }
-  }
-
-  /**
-   * Verifies a module's help.
-   *
-   * Verifies that the module help page from hook_help() exists and can be
-   * displayed, and that it contains the phrase "Foo Bar module", where "Foo
-   * Bar" is the name of the module from the .info.yml file.
-   *
-   * @param string $module
-   *   Machine name of the module to verify.
-   * @param string $name
-   *   Human-readable name of the module to verify.
-   *
-   * @internal
-   */
-  protected function assertHelp(string $module, string $name): void {
-    $this->drupalGet('admin/help/' . $module);
-    $this->assertSession()->statusCodeEquals(200);
-    $this->assertSession()->pageTextContains($name . ' module');
-    $this->assertSession()->linkExists('online documentation for the ' . $name . ' module', 0, "Correct online documentation link is in the help page for $module");
-  }
-
-  /**
-   * Deletes forum taxonomy terms, so Forum can be uninstalled.
-   *
-   * @todo Remove in https://www.drupal.org/project/node/3261652
-   */
-  protected function preUninstallForum() {
-    // There only should be a 'General discussion' term in the 'forums'
-    // vocabulary, but just delete any terms there in case the name changes.
-    $query = \Drupal::entityQuery('taxonomy_term')->accessCheck(FALSE);
-    $query->condition('vid', 'forums');
-    $ids = $query->execute();
-    $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
-    $terms = $storage->loadMultiple($ids);
-    $storage->delete($terms);
-  }
-
-}
diff --git a/core/modules/system/tests/src/Functional/Module/NonStableModulesTest.php b/core/modules/system/tests/src/Functional/Module/NonStableModulesTest.php
index 4250d6350873c601945de06dfe2a3f6c0e9e09cd..239a196eb866ccf419154b470e878fb6e5467327 100644
--- a/core/modules/system/tests/src/Functional/Module/NonStableModulesTest.php
+++ b/core/modules/system/tests/src/Functional/Module/NonStableModulesTest.php
@@ -9,6 +9,7 @@
  * Tests the installation of deprecated and experimental modules.
  *
  * @group Module
+ * @group #slow
  */
 class NonStableModulesTest extends BrowserTestBase {
 
diff --git a/core/modules/system/tests/src/Functional/Routing/RouterTest.php b/core/modules/system/tests/src/Functional/Routing/RouterTest.php
index 674d74171f7abcb3bf37ddec1226cd2992e5a67b..bfcc837236a50598fef4a818058e02fc8b30a51d 100644
--- a/core/modules/system/tests/src/Functional/Routing/RouterTest.php
+++ b/core/modules/system/tests/src/Functional/Routing/RouterTest.php
@@ -14,6 +14,7 @@
  * Functional class for the full integrated routing system.
  *
  * @group Routing
+ * @group #slow
  */
 class RouterTest extends BrowserTestBase {
 
diff --git a/core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php b/core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php
index 9e0a91b292755f09d5872ffb27fd772f16ff6773..aeb9c507c83ef5ee51494247435aeb4288bd110f 100644
--- a/core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php
+++ b/core/modules/system/tests/src/Functional/SecurityAdvisories/SecurityAdvisoryTest.php
@@ -147,6 +147,9 @@ public function testPsa(): void {
     // advisories on admin pages.
     $this->drupalLogin($this->drupalCreateUser([
       'access administration pages',
+      // We have nothing under admin, so we need access to a child route to
+      // access the parent.
+      'administer modules',
     ]));
     $this->assertAdvisoriesNotDisplayed($mixed_advisory_links, ['system.admin']);
 
diff --git a/core/modules/system/tests/src/Functional/Session/SessionHttpsTest.php b/core/modules/system/tests/src/Functional/Session/SessionHttpsTest.php
index a300f3c4436affd430edd18d84ed54640262e8a8..819a3fda94ece52ca8fa9d2eb9a8c797f5585d33 100644
--- a/core/modules/system/tests/src/Functional/Session/SessionHttpsTest.php
+++ b/core/modules/system/tests/src/Functional/Session/SessionHttpsTest.php
@@ -64,7 +64,7 @@ protected function setUp(): void {
    * Tests HTTPS sessions.
    */
   public function testHttpsSession() {
-    $user = $this->drupalCreateUser(['access administration pages']);
+    $user = $this->drupalCreateUser(['access administration pages', 'administer site configuration']);
 
     /** @var \Symfony\Component\BrowserKit\CookieJar $browser_kit_cookie_jar */
     $browser_kit_cookie_jar = $this->getSession()->getDriver()->getClient()->getCookieJar();
diff --git a/core/modules/system/tests/src/Functional/Session/SessionTest.php b/core/modules/system/tests/src/Functional/Session/SessionTest.php
index b5b2d21ce549fb7eb4c813103385aac8b7a02041..7f8cc2c2ca150aaa9cd6124f17d6a2d8d37b0788 100644
--- a/core/modules/system/tests/src/Functional/Session/SessionTest.php
+++ b/core/modules/system/tests/src/Functional/Session/SessionTest.php
@@ -9,6 +9,7 @@
  * Drupal session handling tests.
  *
  * @group Session
+ * @group #slow
  */
 class SessionTest extends BrowserTestBase {
 
diff --git a/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php b/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php
index f322322418cc51c0190c0565897b42206aa4b2f5..9cb689ce40468783623ccb082026289ea374a527 100644
--- a/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php
+++ b/core/modules/system/tests/src/Functional/System/RetrieveFileTest.php
@@ -24,15 +24,15 @@ public function testFileRetrieving() {
     // Test 404 handling by trying to fetch a randomly named file.
     /** @var \Drupal\Core\File\FileSystemInterface $file_system */
     $file_system = \Drupal::service('file_system');
-    $file_system->mkdir($sourcedir = 'public://' . $this->randomMachineName());
+    $file_system->mkdir($source_dir = 'public://' . $this->randomMachineName());
     // cSpell:disable-next-line
     $filename = 'Файл для тестирования ' . $this->randomMachineName();
-    $url = \Drupal::service('file_url_generator')->generateAbsoluteString($sourcedir . '/' . $filename);
+    $url = \Drupal::service('file_url_generator')->generateAbsoluteString($source_dir . '/' . $filename);
     $retrieved_file = system_retrieve_file($url);
     $this->assertFalse($retrieved_file, 'Non-existent file not fetched.');
 
     // Actually create that file, download it via HTTP and test the returned path.
-    file_put_contents($sourcedir . '/' . $filename, 'testing');
+    file_put_contents($source_dir . '/' . $filename, 'testing');
     $retrieved_file = system_retrieve_file($url);
 
     // URLs could not contains characters outside the ASCII set so $filename
@@ -47,15 +47,15 @@ public function testFileRetrieving() {
     $file_system->delete($retrieved_file);
 
     // Test downloading file to a different location.
-    $file_system->mkdir($targetdir = 'temporary://' . $this->randomMachineName());
-    $retrieved_file = system_retrieve_file($url, $targetdir);
-    $this->assertEquals("{$targetdir}/{$encoded_filename}", $retrieved_file, 'Sane path for downloaded file returned (temporary:// scheme).');
+    $file_system->mkdir($target_dir = 'temporary://' . $this->randomMachineName());
+    $retrieved_file = system_retrieve_file($url, $target_dir);
+    $this->assertEquals("{$target_dir}/{$encoded_filename}", $retrieved_file, 'Sane path for downloaded file returned (temporary:// scheme).');
     $this->assertFileExists($retrieved_file);
     $this->assertEquals(7, filesize($retrieved_file), 'File size of downloaded file is correct (temporary:// scheme).');
     $file_system->delete($retrieved_file);
 
-    $file_system->deleteRecursive($sourcedir);
-    $file_system->deleteRecursive($targetdir);
+    $file_system->deleteRecursive($source_dir);
+    $file_system->deleteRecursive($target_dir);
   }
 
 }
diff --git a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
index 764df74e68583db0b99f5a257451fce1dd2b550c..0db7654cf2982b12a9b5452c05b2b463b282caad 100644
--- a/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
+++ b/core/modules/system/tests/src/Functional/UpdateSystem/UpdateScriptTest.php
@@ -12,6 +12,7 @@
  * Tests the update script access and functionality.
  *
  * @group Update
+ * @group #slow
  */
 class UpdateScriptTest extends BrowserTestBase {
 
diff --git a/core/modules/system/tests/src/FunctionalJavascript/NoJavaScriptAnonymousTest.php b/core/modules/system/tests/src/FunctionalJavascript/NoJavaScriptAnonymousTest.php
index 6bccdb95a8d56da4119886200e71c4fde8ec1050..51fee59063bede932d03f983568ad92467f84729 100644
--- a/core/modules/system/tests/src/FunctionalJavascript/NoJavaScriptAnonymousTest.php
+++ b/core/modules/system/tests/src/FunctionalJavascript/NoJavaScriptAnonymousTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\system\FunctionalJavascript;
 
+use Drupal\Tests\PerformanceData;
 use Drupal\FunctionalJavascriptTests\PerformanceTestBase;
 use Drupal\node\NodeInterface;
 
@@ -46,30 +47,39 @@ public function testNoJavaScript() {
     ]);
 
     // Test frontpage.
-    $this->drupalGet('');
-    $this->assertNoJavaScript();
+    $performance_data = $this->collectPerformanceData(function () {
+      $this->drupalGet('');
+    });
+    $this->assertNoJavaScript($performance_data);
 
     // Test node page.
-    $this->drupalGet('node/1');
-    $this->assertNoJavaScript();
+    $performance_data = $this->collectPerformanceData(function () {
+      $this->drupalGet('node/1');
+    });
+    $this->assertNoJavaScript($performance_data);
 
     // Test user profile page.
     $user = $this->drupalCreateUser();
-    $this->drupalGet('user/' . $user->id());
-    $this->assertNoJavaScript();
+    $performance_data = $this->collectPerformanceData(function () use ($user) {
+      $this->drupalGet('user/' . $user->id());
+    });
+    $this->assertNoJavaScript($performance_data);
   }
 
   /**
    * Passes if no JavaScript is found on the page.
    *
+   * @param Drupal\Tests\PerformanceData $performance_data
+   *   A PerformanceData value object.
+   *
    * @internal
    */
-  protected function assertNoJavaScript(): void {
+  protected function assertNoJavaScript(PerformanceData $performance_data): void {
     // Ensure drupalSettings is not set.
     $settings = $this->getDrupalSettings();
     $this->assertEmpty($settings, 'drupalSettings is not set.');
     $this->assertSession()->responseNotMatches('/\.js/');
-    $this->assertSame(0, $this->scriptCount);
+    $this->assertSame(0, $performance_data->getScriptCount());
   }
 
 }
diff --git a/core/modules/system/tests/src/Kernel/Form/FormObjectTest.php b/core/modules/system/tests/src/Kernel/Form/FormObjectTest.php
index ecf7c5047d94241cd82f37a15703e5b39cbb3cb7..5f4553698e46b6e327a2d8ed1598049945f60ade 100644
--- a/core/modules/system/tests/src/Kernel/Form/FormObjectTest.php
+++ b/core/modules/system/tests/src/Kernel/Form/FormObjectTest.php
@@ -25,7 +25,7 @@ class FormObjectTest extends ConfigFormTestBase {
   protected function setUp(): void {
     parent::setUp();
 
-    $this->form = new FormTestObject($this->container->get('config.factory'));
+    $this->form = new FormTestObject($this->container->get('config.factory'), $this->container->get('config.typed'));
     $this->values = [
       'bananas' => [
         '#value' => $this->randomString(10),
diff --git a/core/modules/system/tests/src/Kernel/Mail/MailTest.php b/core/modules/system/tests/src/Kernel/Mail/MailTest.php
index 8398fd24ebd64160dcfe606342b25d705f9b3a7b..3b99732d6971ad389d2be005b6b00080c14f5348 100644
--- a/core/modules/system/tests/src/Kernel/Mail/MailTest.php
+++ b/core/modules/system/tests/src/Kernel/Mail/MailTest.php
@@ -324,7 +324,7 @@ public function testRenderedElementsUseAbsolutePaths() {
         '#theme' => 'image',
         '#uri' => $input_path,
       ];
-      $expected_html = "<img src=\"$expected_path\" alt=\"\" />";
+      $expected_html = "<img src=\"$expected_path\" alt>\n";
 
       // Send a test message that mail_cancel_test_mail_alter should cancel.
       \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
diff --git a/core/modules/system/tests/src/Kernel/Theme/ThemeTest.php b/core/modules/system/tests/src/Kernel/Theme/ThemeTest.php
index c3d1b252b164bdc8650a38a34a97d9aa2fc5aa69..da25f5d65ab6140d9566454cee64c510cb17df98 100644
--- a/core/modules/system/tests/src/Kernel/Theme/ThemeTest.php
+++ b/core/modules/system/tests/src/Kernel/Theme/ThemeTest.php
@@ -55,8 +55,8 @@ public function testThemeDataTypes() {
     // theme_test_false is an implemented theme hook so \Drupal::theme() service
     // should return a string or an object that implements MarkupInterface,
     // even though the theme function itself can return anything.
-    $foos = ['null' => NULL, 'false' => FALSE, 'integer' => 1, 'string' => 'foo', 'empty_string' => ''];
-    foreach ($foos as $type => $example) {
+    $types = ['null' => NULL, 'false' => FALSE, 'integer' => 1, 'string' => 'foo', 'empty_string' => ''];
+    foreach ($types as $type => $example) {
       $output = \Drupal::theme()->render('theme_test_foo', ['foo' => $example]);
       $this->assertTrue($output instanceof MarkupInterface || is_string($output), new FormattableMarkup('\Drupal::theme() returns an object that implements MarkupInterface or a string for data type @type.', ['@type' => $type]));
       if ($output instanceof MarkupInterface) {
diff --git a/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php b/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php
index 4623ffc9c7d44838ab7aa65026c661b8d4d064be..cf210b4ec8aa3df7de0c05061eaf0e0c9dbfd97c 100644
--- a/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php
+++ b/core/modules/system/tests/src/Unit/Pager/PreprocessPagerTest.php
@@ -88,7 +88,7 @@ public function testQuantitySet() {
     /** @var \Drupal\Core\Template\AttributeString $attribute */
     $attribute = $variables['items']['pages']['2']['attributes']->offsetGet('aria-current');
     $this->assertInstanceOf(AttributeString::class, $attribute);
-    $this->assertEquals('Current page', $attribute->value());
+    $this->assertEquals('page', $attribute->value());
   }
 
 }
diff --git a/core/modules/taxonomy/migrations/d6_taxonomy_vocabulary.yml b/core/modules/taxonomy/migrations/d6_taxonomy_vocabulary.yml
index f4a3ec1355fcabb0be8cadbe71cb9982a914ea6a..1a0044345e583157a0f78e598b00a8b8c845a26c 100644
--- a/core/modules/taxonomy/migrations/d6_taxonomy_vocabulary.yml
+++ b/core/modules/taxonomy/migrations/d6_taxonomy_vocabulary.yml
@@ -16,12 +16,6 @@ process:
       field: vid
       length: 30
       migrated: true
-    -
-      # This plugin checks if the vocabulary being migrated is the one used by
-      # Forum. If so, we use the machine name that Forum expects. Otherwise, we
-      # leave it unchanged.
-      plugin: forum_vocabulary
-      machine_name: forums
   label: name
   name: name
   description: description
diff --git a/core/modules/taxonomy/migrations/d6_vocabulary_entity_display.yml b/core/modules/taxonomy/migrations/d6_vocabulary_entity_display.yml
index 760b6dde39d3096a893514d4f6ed86ca2388863a..50e66e61145481fe30020da245f4f77f7a20eed0 100644
--- a/core/modules/taxonomy/migrations/d6_vocabulary_entity_display.yml
+++ b/core/modules/taxonomy/migrations/d6_vocabulary_entity_display.yml
@@ -45,12 +45,6 @@ process:
     -
       plugin: substr
       length: 32
-    -
-      # This plugin checks if the vocabulary being migrated is the one used by
-      # Forum. If so, we use the machine name that Forum expects. Otherwise, we
-      # leave it unchanged.
-      plugin: forum_vocabulary
-      machine_name: taxonomy_forums
 destination:
   plugin: component_entity_display
 migration_dependencies:
diff --git a/core/modules/taxonomy/migrations/d6_vocabulary_entity_form_display.yml b/core/modules/taxonomy/migrations/d6_vocabulary_entity_form_display.yml
index da8cd2704b9b805828707f185cc72b67a95f451e..c33d85b7a95cde519997716fdcc7a87212d510f2 100644
--- a/core/modules/taxonomy/migrations/d6_vocabulary_entity_form_display.yml
+++ b/core/modules/taxonomy/migrations/d6_vocabulary_entity_form_display.yml
@@ -49,12 +49,6 @@ process:
     -
       plugin: substr
       length: 32
-    -
-      # This plugin checks if the vocabulary being migrated is the one used by
-      # Forum. If so, we use the machine name that Forum expects. Otherwise, we
-      # leave it unchanged.
-      plugin: forum_vocabulary
-      machine_name: taxonomy_forums
 destination:
   plugin: component_entity_form_display
 migration_dependencies:
diff --git a/core/modules/taxonomy/migrations/d6_vocabulary_field.yml b/core/modules/taxonomy/migrations/d6_vocabulary_field.yml
index 36974e159c7286f1400496eb0a39f130be4f84ef..d6730eded8562eb4752647b213138674ec012ed0 100644
--- a/core/modules/taxonomy/migrations/d6_vocabulary_field.yml
+++ b/core/modules/taxonomy/migrations/d6_vocabulary_field.yml
@@ -33,12 +33,6 @@ process:
     -
       plugin: substr
       length: 32
-    -
-      # This plugin checks if the vocabulary being migrated is the one used by
-      # Forum. If so, we use the machine name that Forum expects. Otherwise, we
-      # leave it unchanged.
-      plugin: forum_vocabulary
-      machine_name: taxonomy_forums
   'settings/target_type': 'constants/target_entity_type'
   cardinality: cardinality
 destination:
diff --git a/core/modules/taxonomy/migrations/d6_vocabulary_field_instance.yml b/core/modules/taxonomy/migrations/d6_vocabulary_field_instance.yml
index ac3ed4e08b4cfb50330c45eba46bfa8dc351a801..a88ffb4fdd3e1be9d4d3ed26386bba4a65acd993 100644
--- a/core/modules/taxonomy/migrations/d6_vocabulary_field_instance.yml
+++ b/core/modules/taxonomy/migrations/d6_vocabulary_field_instance.yml
@@ -41,12 +41,6 @@ process:
     -
       plugin: substr
       length: 32
-    -
-      # This plugin checks if the vocabulary being migrated is the one used by
-      # Forum. If so, we use the machine name that Forum expects. Otherwise, we
-      # leave it unchanged.
-      plugin: forum_vocabulary
-      machine_name: taxonomy_forums
   label: name
   _vid:
     -
diff --git a/core/modules/taxonomy/migrations/d7_taxonomy_term.yml b/core/modules/taxonomy/migrations/d7_taxonomy_term.yml
index 1bae2d6e32f9d8e6903d3db377b88c16164b877c..163dd454409d419698e76799b38599bff5e753ae 100644
--- a/core/modules/taxonomy/migrations/d7_taxonomy_term.yml
+++ b/core/modules/taxonomy/migrations/d7_taxonomy_term.yml
@@ -32,7 +32,6 @@ process:
     plugin: default_value
     default_value: 0
     source: '@parent_id'
-  forum_container: is_container
   changed: timestamp
   langcode: language
 destination:
diff --git a/core/modules/taxonomy/migrations/d7_taxonomy_vocabulary.yml b/core/modules/taxonomy/migrations/d7_taxonomy_vocabulary.yml
index e9c811d1665cc7106e0ac15105026bb74b4a7f6f..c26c14a98f28ff29c9b282568acbd8b9a44bd19b 100644
--- a/core/modules/taxonomy/migrations/d7_taxonomy_vocabulary.yml
+++ b/core/modules/taxonomy/migrations/d7_taxonomy_vocabulary.yml
@@ -7,19 +7,12 @@ source:
   plugin: d7_taxonomy_vocabulary
 process:
   vid:
-    -
-      plugin: make_unique_entity_field
-      source: machine_name
-      entity_type: taxonomy_vocabulary
-      field: vid
-      length: 30
-      migrated: true
-    -
-      # This plugin checks if the vocabulary being migrated is the one used by
-      # Forum. If so, we use the machine name that Forum expects. Otherwise, we
-      # leave it unchanged.
-      plugin: forum_vocabulary
-      machine_name: forums
+    plugin: make_unique_entity_field
+    source: machine_name
+    entity_type: taxonomy_vocabulary
+    field: vid
+    length: 30
+    migrated: true
   label: name
   name: name
   description: description
diff --git a/core/modules/taxonomy/src/Plugin/migrate/process/ForumVocabulary.php b/core/modules/taxonomy/src/Plugin/migrate/process/ForumVocabulary.php
index 677f246519fcec6ec982c3cac2fe1d7a049036c6..7ea58afb2001815e47e48c5e68090596f38d645a 100644
--- a/core/modules/taxonomy/src/Plugin/migrate/process/ForumVocabulary.php
+++ b/core/modules/taxonomy/src/Plugin/migrate/process/ForumVocabulary.php
@@ -27,12 +27,28 @@
  *     machine_name: taxonomy_forums
  * @endcode
  *
- * @MigrateProcessPlugin(
- *   id = "forum_vocabulary"
- * )
+ * @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use
+ *   \Drupal\forum\Plugin\migrate\process\ForumVocabulary instead.
+ *
+ * @see https://www.drupal.org/node/3387830
  */
 class ForumVocabulary extends ProcessPluginBase {
 
+  /**
+   * Constructs a MigrationLookup object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+    @trigger_error(__CLASS__ . 'is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use \Drupal\forum\Plugin\migrate\process\ForumVocabulary instead. See https://www.drupal.org/node/3387830', E_USER_DEPRECATED);
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php
index 40c7a6bcb9171673b777c3f42a4ea06fa0d9b615..edb74619b4e67fe5636a696f9c465599dd1ce853 100644
--- a/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php
+++ b/core/modules/taxonomy/src/Plugin/migrate/source/d7/Term.php
@@ -132,11 +132,6 @@ public function prepareRow(Row $row) {
       ->fetchCol();
     $row->setSourceProperty('parent', $parents);
 
-    // Determine if this is a forum container.
-    $forum_container_tids = $this->variableGet('forum_containers', []);
-    $current_tid = $row->getSourceProperty('tid');
-    $row->setSourceProperty('is_container', in_array($current_tid, $forum_container_tids));
-
     // If the term name or term description were replaced by real fields using
     // the Drupal 7 Title module, use the fields value instead of the term name
     // or term description.
diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermEntityTranslation.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermEntityTranslation.php
index 594cde8fe9935d95e49fa959b940904f6f9b246a..88cb561b8537249c9cd8a0e8cbd19674f7ffb294 100644
--- a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermEntityTranslation.php
+++ b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermEntityTranslation.php
@@ -103,11 +103,6 @@ public function prepareRow(Row $row) {
         $row->setSourceProperty('format', $description_field[0]['format']);
       }
     }
-
-    // Determine if this is a forum container.
-    $forum_container_tids = $this->variableGet('forum_containers', []);
-    $row->setSourceProperty('is_container', in_array($tid, $forum_container_tids));
-
     return parent::prepareRow($row);
   }
 
diff --git a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
index 84c0533663bf99dbf5c971441e612c95539175d4..d83a028eddcf109699686b1a828d560975522bba 100644
--- a/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
+++ b/core/modules/taxonomy/src/Plugin/views/filter/TaxonomyIndexTid.php
@@ -400,19 +400,6 @@ public function adminSummary() {
     return parent::adminSummary();
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheContexts() {
-    $contexts = parent::getCacheContexts();
-    // The result potentially depends on term access and so is just cacheable
-    // per user.
-    // @todo See https://www.drupal.org/node/2352175.
-    $contexts[] = 'user';
-
-    return $contexts;
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 5f011c6f9849ae8b87549cc5228ecd970cd5f461..1ba3200d5f98223e5367f55da768c1aa68403e73 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -205,7 +205,7 @@ function taxonomy_build_node_index($node) {
       $connection = \Drupal::database();
       foreach ($tid_all as $tid) {
         $connection->merge('taxonomy_index')
-          ->key(['nid' => $node->id(), 'tid' => $tid, 'status' => $node->isPublished()])
+          ->keys(['nid' => $node->id(), 'tid' => $tid, 'status' => $node->isPublished()])
           ->fields(['sticky' => $sticky, 'created' => $node->getCreatedTime()])
           ->execute();
       }
diff --git a/core/modules/taxonomy/tests/src/Functional/GenericTest.php b/core/modules/taxonomy/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8d522ec8bb5b6619a077fc0b53b1607ac8a1734f
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for taxonomy.
+ *
+ * @group taxonomy
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/taxonomy/tests/src/Functional/Rest/TermJsonBasicAuthTest.php b/core/modules/taxonomy/tests/src/Functional/Rest/TermJsonBasicAuthTest.php
index 87aff1ce1ab5ed684575d3ecf46c63c5812046d2..ab4e5175d9f0c104b6ee7135593c73825c9c8dd9 100644
--- a/core/modules/taxonomy/tests/src/Functional/Rest/TermJsonBasicAuthTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/Rest/TermJsonBasicAuthTest.php
@@ -6,6 +6,7 @@
 
 /**
  * @group rest
+ * @group #slow
  */
 class TermJsonBasicAuthTest extends TermResourceTestBase {
 
diff --git a/core/modules/taxonomy/tests/src/Functional/Rest/TermXmlBasicAuthTest.php b/core/modules/taxonomy/tests/src/Functional/Rest/TermXmlBasicAuthTest.php
index 7ff2733ee8e531c5de547dc97a976beecead425b..5bd7f5ba46ecf04af81a3768d56fc3c19e3617e8 100644
--- a/core/modules/taxonomy/tests/src/Functional/Rest/TermXmlBasicAuthTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/Rest/TermXmlBasicAuthTest.php
@@ -7,6 +7,7 @@
 
 /**
  * @group rest
+ * @group #slow
  */
 class TermXmlBasicAuthTest extends TermResourceTestBase {
 
diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php
index 459400356bba8240079061c2ad7c6f4abbafd2a4..ccb9cbca3a38051bbf857195b214f0e46dcd29bf 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d6/MigrateTaxonomyTermTest.php
@@ -15,14 +15,13 @@ class MigrateTaxonomyTermTest extends MigrateDrupal6TestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['comment', 'forum', 'taxonomy'];
+  protected static $modules = ['comment', 'taxonomy'];
 
   /**
    * {@inheritdoc}
    */
   protected function setUp(): void {
     parent::setUp();
-    $this->installConfig('forum');
     $this->installEntitySchema('taxonomy_term');
     $this->executeMigrations(['d6_taxonomy_vocabulary', 'd6_taxonomy_term']);
   }
@@ -114,10 +113,6 @@ public function testTaxonomyTerms() {
       sort($actual_parents);
       $this->assertEquals($expected_parents, $actual_parents, "Term $tid has correct parents in vocabulary tree");
     }
-
-    // Check the forum is not a container.
-    $term = Term::load(8);
-    $this->assertEquals(0, $term->forum_container->value);
   }
 
 }
diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php
index e90ba79ee5aa713b2d68f38d4dacd83a2f1bcf5e..a7f4ea69e861d1e693a7474f80b4a7eb00a8ab9a 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTest.php
@@ -15,7 +15,6 @@ class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
 
   protected static $modules = [
     'comment',
-    'forum',
     'content_translation',
     'datetime',
     'datetime_range',
@@ -41,7 +40,6 @@ class MigrateTaxonomyTermTest extends MigrateDrupal7TestBase {
    */
   protected function setUp(): void {
     parent::setUp();
-    $this->installConfig('forum');
     $this->installEntitySchema('comment');
     $this->installEntitySchema('file');
 
@@ -103,16 +101,13 @@ protected function assertEntity(int $id, string $expected_language, string $expe
       $this->assertTrue($entity->hasField('field_integer'));
       $this->assertEquals($expected_term_reference_tid, $entity->field_term_reference->target_id);
     }
-    if (isset($expected_container_flag)) {
-      $this->assertEquals($expected_container_flag, $entity->forum_container->value);
-    }
   }
 
   /**
    * Tests the Drupal 7 taxonomy term to Drupal 8 migration.
    */
   public function testTaxonomyTerms() {
-    $this->assertEntity(1, 'en', 'General discussion', 'forums', '', NULL, 2);
+    $this->assertEntity(1, 'en', 'General discussion', 'sujet_de_discussion', '', NULL, 2);
 
     // Tests that terms that used the Drupal 7 Title module and that have their
     // name and description replaced by real fields are correctly migrated.
@@ -120,18 +115,10 @@ public function testTaxonomyTerms() {
 
     $this->assertEntity(3, 'en', 'Term2', 'test_vocabulary', 'The second term.', 'filtered_html');
     $this->assertEntity(4, 'en', 'Term3 in plain old English', 'test_vocabulary', 'The third term in plain old English.', 'full_html', 0, [3], 6);
-    $this->assertEntity(5, 'en', 'Custom Forum', 'forums', 'Where the cool kids are.', NULL, 3, [], NULL, NULL, 0);
-    $this->assertEntity(6, 'en', 'Games', 'forums', NULL, '', 4, [], NULL, NULL, 1);
-    $this->assertEntity(7, 'en', 'Minecraft', 'forums', '', NULL, 1, [6], NULL, NULL, 0);
-    $this->assertEntity(8, 'en', 'Half Life 3', 'forums', '', NULL, 0, [6], NULL, NULL, 0);
-
-    // Verify that we still can create forum containers after the migration.
-    $term = Term::create(['vid' => 'forums', 'name' => 'Forum Container', 'forum_container' => 1]);
-    $term->save();
-
-    // Reset the forums tree data so this new term is included in the tree.
-    unset($this->treeData['forums']);
-    $this->assertEntity(26, 'en', 'Forum Container', 'forums', '', '', 0, [], NULL, NULL, 1);
+    $this->assertEntity(5, 'en', 'Custom Forum', 'sujet_de_discussion', 'Where the cool kids are.', NULL, 3, [], NULL, NULL, 0);
+    $this->assertEntity(6, 'en', 'Games', 'sujet_de_discussion', NULL, '', 4, [], NULL, NULL, 1);
+    $this->assertEntity(7, 'en', 'Minecraft', 'sujet_de_discussion', '', NULL, 1, [6], NULL, NULL, 0);
+    $this->assertEntity(8, 'en', 'Half Life 3', 'sujet_de_discussion', '', NULL, 0, [6], NULL, NULL, 0);
 
     // Test taxonomy term language translations.
     $this->assertEntity(19, 'en', 'Jupiter Station', 'vocablocalized', 'Holographic research.', 'filtered_html', 0, [], NULL, NULL);
diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTranslationTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTranslationTest.php
index f8acceff8dd8a7e306a8f6fb461c25c9eab44ea6..98137eb8a359825d2f36af4d74a4443d43bc42fa 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTranslationTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyTermTranslationTest.php
@@ -132,11 +132,11 @@ protected function assertHierarchy(string $vid, int $tid, array $parent_ids): vo
    */
   public function testTaxonomyTermTranslation() {
     // Forums vocabulary, no multilingual option.
-    $this->assertEntity(1, 'en', 'General discussion', 'forums', NULL, NULL, '2', []);
-    $this->assertEntity(5, 'en', 'Custom Forum', 'forums', 'Where the cool kids are.', NULL, '3', []);
-    $this->assertEntity(6, 'en', 'Games', 'forums', NULL, NULL, '4', []);
-    $this->assertEntity(7, 'en', 'Minecraft', 'forums', NULL, NULL, '1', ['6']);
-    $this->assertEntity(8, 'en', 'Half Life 3', 'forums', NULL, NULL, '0', ['6']);
+    $this->assertEntity(1, 'en', 'General discussion', 'sujet_de_discussion', NULL, NULL, '2', []);
+    $this->assertEntity(5, 'en', 'Custom Forum', 'sujet_de_discussion', 'Where the cool kids are.', NULL, '3', []);
+    $this->assertEntity(6, 'en', 'Games', 'sujet_de_discussion', NULL, NULL, '4', []);
+    $this->assertEntity(7, 'en', 'Minecraft', 'sujet_de_discussion', NULL, NULL, '1', ['6']);
+    $this->assertEntity(8, 'en', 'Half Life 3', 'sujet_de_discussion', NULL, NULL, '0', ['6']);
 
     // Test vocabulary, field translation.
     $this->assertEntity(2, 'en', 'Term1 (This is a real field!)', 'test_vocabulary', 'The first term. (This is a real field!)', 'filtered_html', '0', []);
diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php
index 979d5b5ac1fb4192cc0f48196c3f99eb96fd5b46..d90b71b905276474acc02aa69a73d4f3279df736 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTaxonomyVocabularyTest.php
@@ -54,7 +54,7 @@ protected function assertEntity(string $id, string $expected_label, string $expe
    */
   public function testTaxonomyVocabulary() {
     $this->assertEntity('tags', 'Tags', 'Use tags to group articles on similar topics into categories.', 0);
-    $this->assertEntity('forums', 'Sujet de discussion', 'Forum navigation vocabulary', -10);
+    $this->assertEntity('sujet_de_discussion', 'Sujet de discussion', 'Forum navigation vocabulary', -10);
     $this->assertEntity('test_vocabulary', 'Test Vocabulary', 'This is the vocabulary description', 0);
     $this->assertEntity('vocabulary_name_much_longer_th', 'vocabulary name clearly different than machine name and much longer than thirty two characters', 'description of vocabulary name much longer than thirty two characters', 0);
   }
diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermEntityTranslationTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermEntityTranslationTest.php
index d8c48e840f3be3401a6195de3ec53c5cdf367c72..9cb061d19895afbcabdf9ea84fffd0cfbfe162a7 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermEntityTranslationTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermEntityTranslationTest.php
@@ -295,7 +295,6 @@ public function providerSource() {
         'description' => 'Term Description FR',
         'format' => 'full_html',
         'machine_name' => 'tags',
-        'is_container' => FALSE,
         'field_test' => [
           [
             'value' => 'French field',
@@ -318,7 +317,6 @@ public function providerSource() {
         'description' => 'Term Description ES',
         'format' => 'full_html',
         'machine_name' => 'tags',
-        'is_container' => FALSE,
         'field_test' => [
           [
             'value' => 'Spanish field',
diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermLocalizedTranslationTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermLocalizedTranslationTest.php
index f9bce5525775e5efcf67d4f21db293ee276063fe..1447eda0b510f0ee75c54c59508194ea99e22790 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermLocalizedTranslationTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermLocalizedTranslationTest.php
@@ -101,7 +101,6 @@ public function providerSource() {
         'name' => 'name value 1 (name_field)',
         'description' => 'description value 1 (description_field)',
         'weight' => 0,
-        'is_container' => '',
         'language' => 'fr',
         'i18n_tsid' => '0',
         'machine_name' => 'tags',
@@ -119,7 +118,6 @@ public function providerSource() {
         'name' => 'name value 1 (name_field)',
         'description' => 'description value 1 (description_field)',
         'weight' => 0,
-        'is_container' => '',
         'language' => 'fr',
         'i18n_tsid' => '0',
         'machine_name' => 'tags',
@@ -182,7 +180,6 @@ public function providerSource() {
         'name' => 'name value 3',
         'description' => 'description value 3',
         'weight' => 0,
-        'is_container' => '',
         'language' => 'zu',
         'i18n_tsid' => '0',
         'machine_name' => 'categories',
@@ -198,7 +195,6 @@ public function providerSource() {
         'name' => 'name value 5',
         'description' => 'description value 5',
         'weight' => 1,
-        'is_container' => '1',
         'language' => 'fr',
         'i18n_tsid' => '0',
         'machine_name' => 'categories',
@@ -216,7 +212,6 @@ public function providerSource() {
         'name' => 'name value 5',
         'description' => 'description value 5',
         'weight' => 1,
-        'is_container' => '1',
         'language' => 'fr',
         'i18n_tsid' => '0',
         'machine_name' => 'categories',
diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTest.php
index 2c2365b26c54acffdf2d4bfc25831c59157a9858..8ee71dfcc218e2832a20cc76237500d065770a66 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTest.php
@@ -31,7 +31,6 @@ public function providerSource() {
         'name' => 'name value 1',
         'description' => 'description value 1',
         'weight' => 0,
-        'is_container' => FALSE,
       ],
       [
         'tid' => 2,
@@ -39,7 +38,6 @@ public function providerSource() {
         'name' => 'name value 2',
         'description' => 'description value 2',
         'weight' => 0,
-        'is_container' => TRUE,
       ],
       [
         'tid' => 3,
@@ -47,7 +45,6 @@ public function providerSource() {
         'name' => 'name value 3',
         'description' => 'description value 3',
         'weight' => 0,
-        'is_container' => FALSE,
       ],
       [
         'tid' => 4,
@@ -55,7 +52,6 @@ public function providerSource() {
         'name' => 'name value 4',
         'description' => 'description value 4',
         'weight' => 1,
-        'is_container' => FALSE,
       ],
       [
         'tid' => 5,
@@ -63,7 +59,6 @@ public function providerSource() {
         'name' => 'name value 5',
         'description' => 'description value 5',
         'weight' => 1,
-        'is_container' => FALSE,
       ],
       [
         'tid' => 6,
@@ -71,7 +66,6 @@ public function providerSource() {
         'name' => 'name value 6',
         'description' => 'description value 6',
         'weight' => 0,
-        'is_container' => TRUE,
       ],
       [
         'tid' => 7,
@@ -79,7 +73,6 @@ public function providerSource() {
         'name' => 'name value 7',
         'description' => 'description value 7',
         'weight' => 0,
-        'is_container' => TRUE,
       ],
     ];
     $tests[0]['source_data']['taxonomy_term_hierarchy'] = [
diff --git a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTranslationTest.php b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTranslationTest.php
index 2008546d76f8d2381f81582d3645c09e16603336..9e37649da850adaf265f122c75de5eb9bcb52bce 100644
--- a/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTranslationTest.php
+++ b/core/modules/taxonomy/tests/src/Kernel/Plugin/migrate/source/d7/TermTranslationTest.php
@@ -31,7 +31,6 @@ public function providerSource() {
         'name' => 'fr - name 1',
         'description' => 'desc 1',
         'weight' => 0,
-        'is_container' => FALSE,
         'language' => 'fr',
         'i18n_tsid' => '1',
       ],
@@ -41,7 +40,6 @@ public function providerSource() {
         'name' => 'name 2',
         'description' => 'desc 2',
         'weight' => 0,
-        'is_container' => TRUE,
         'language' => 'en',
         'i18n_tsid' => '1',
       ],
@@ -51,7 +49,6 @@ public function providerSource() {
         'name' => 'name 3',
         'description' => 'desc 3',
         'weight' => 0,
-        'is_container' => FALSE,
         'language' => '',
         'i18n_tsid' => '',
       ],
@@ -61,7 +58,6 @@ public function providerSource() {
         'name' => 'is - name 4',
         'description' => 'desc 4',
         'weight' => 1,
-        'is_container' => FALSE,
         'language' => 'is',
         'i18n_tsid' => '1',
       ],
@@ -71,7 +67,6 @@ public function providerSource() {
         'name' => 'name 5',
         'description' => 'desc 5',
         'weight' => 1,
-        'is_container' => FALSE,
         'language' => '',
         'i18n_tsid' => '',
       ],
@@ -81,7 +76,6 @@ public function providerSource() {
         'name' => 'name 6',
         'description' => 'desc 6',
         'weight' => 0,
-        'is_container' => TRUE,
         'language' => '',
         'i18n_tsid' => '',
       ],
@@ -91,7 +85,6 @@ public function providerSource() {
         'name' => 'is - captains',
         'description' => 'desc 7',
         'weight' => 0,
-        'is_container' => TRUE,
         'language' => 'is',
         'i18n_tsid' => '',
       ],
@@ -277,7 +270,6 @@ public function providerSource() {
         'name' => 'fr - name 1',
         'description' => 'desc 1',
         'weight' => 0,
-        'is_container' => '',
         'language' => 'fr',
         'i18n_tsid' => '1',
         'machine_name' => 'tags',
@@ -291,7 +283,6 @@ public function providerSource() {
         'name' => 'name 2',
         'description' => 'desc 2',
         'weight' => 0,
-        'is_container' => '',
         'language' => 'en',
         'i18n_tsid' => '1',
         'machine_name' => 'tags',
@@ -305,7 +296,6 @@ public function providerSource() {
         'name' => 'is - name 4',
         'description' => 'desc 4',
         'weight' => 1,
-        'is_container' => '',
         'language' => 'is',
         'i18n_tsid' => '1',
         'machine_name' => 'tags',
@@ -336,7 +326,6 @@ public function providerSource() {
       'name' => 'is - captains',
       'description' => 'desc 7',
       'weight' => 0,
-      'is_container' => '',
       'language' => 'is',
       'i18n_tsid' => '',
       'machine_name' => 'categories',
diff --git a/core/modules/telephone/css/telephone.icon.theme.css b/core/modules/telephone/css/telephone.icon.theme.css
index 23a41035c68e9452521b8c572f93a406abcfa213..7d9335e3fef9f4593d4c0b80ba4a3abcc993eaea 100644
--- a/core/modules/telephone/css/telephone.icon.theme.css
+++ b/core/modules/telephone/css/telephone.icon.theme.css
@@ -5,5 +5,5 @@
  * @preserve
  */
 .field-icon-telephone {
-  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' fill='none'%3e  %3cpath d='M4.548 7.943a14.422 14.422 0 0 0 7.55 7.397c.552-.4.863-1.095 1.235-1.66.278-.275.834-.81 1.39-.55 1.381.65 2.04.877 3.61 1.1.155.021.579.164.834.19.383.037.833.36.833 1.136v3.07c0 .55-.451 1.282-1.033 1.323-.486.034-.882.051-1.19.051C7.918 20 0 11.862 0 2.198c0-.303.017-.696.052-1.176C.092.446.833 0 1.389 0h3.104c.785 0 1.11.445 1.149.824.025.252.17.672.191.825.225 1.552.455 2.205 1.111 3.572.265.55-.277 1.099-.555 1.374-.6.387-1.476.733-1.84 1.348Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m9.82246 14.9148c2.20964 4.9719 6.27314 8.9558 11.32454 11.0949.8278-.5984 1.2952-1.6427 1.853-2.4896.4167-.4122 1.25-1.2162 2.0833-.8243 2.0728.9746 3.0622 1.3152 5.4167 1.6486.2314.0328.8678.2463 1.25.2844.5749.0574 1.25.54 1.25 1.7046v4.6058c0 .8243-.6765 1.9233-1.5492 1.9844-.7289.0509-1.3237.0764-1.7841.0764-14.7917 0-26.6667-12.2065-26.6667-26.70265 0-.45554.02574-1.0438.07726-1.7649.0617-.86328 1.17274-1.53245 2.00607-1.53245h4.65592c1.17745 0 1.66525.66786 1.72325 1.23651.0386.37783.2543 1.00757.2875 1.23651.3371 2.32911.6814 3.30781 1.6667 5.35818.3961.8244-.4167 1.6487-.8334 2.0609-.9004.5803-2.2133 1.1-2.76084 2.0227z' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/telephone/telephone.module b/core/modules/telephone/telephone.module
index c89d8bd83fd93e4ae4b06345523a7b399eb120ba..94502452a1ecc9b784dffda76c98325fb1caf203 100644
--- a/core/modules/telephone/telephone.module
+++ b/core/modules/telephone/telephone.module
@@ -5,6 +5,7 @@
  * Defines a simple telephone number field type.
  */
 
+use Drupal\Core\Field\FieldTypeCategoryManagerInterface;
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 
@@ -36,8 +37,10 @@ function telephone_field_formatter_info_alter(&$info) {
 }
 
 /**
- * Implements hook_preprocess_form_element__new_storage_type().
+ * Implements hook_field_type_category_info_alter().
  */
-function telephone_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'telephone/drupal.telephone-icon';
+function telephone_field_type_category_info_alter(&$definitions) {
+  // The `telephone` field type belongs in the `general` category, so the
+  // libraries need to be attached using an alter hook.
+  $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'telephone/drupal.telephone-icon';
 }
diff --git a/core/modules/telephone/tests/src/Functional/GenericTest.php b/core/modules/telephone/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f46b44c8880f5b686b3027afff17fb129940d5c0
--- /dev/null
+++ b/core/modules/telephone/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\telephone\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for telephone.
+ *
+ * @group telephone
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/text/css/text.icon.theme.css b/core/modules/text/css/text.icon.theme.css
index 2b064e4acc18e45843895585753c584d1c8fd850..bca6e90092df50f90063455714e74a47c9e67876 100644
--- a/core/modules/text/css/text.icon.theme.css
+++ b/core/modules/text/css/text.icon.theme.css
@@ -5,5 +5,5 @@
  * @preserve
  */
 .field-icon-formatted_text {
-  background-image: url("data:image/svg+xml,%3csvg width='24' height='14' fill='none' xmlns='http://www.w3.org/2000/svg'%3e  %3cpath fill-rule='evenodd' clip-rule='evenodd' d='M24 2H14V0h10v2ZM24 8H11V6h13v2ZM24 14H9v-2h15v2Z' fill='%2355565B'/%3e  %3cpath d='M0 13c.612 0 1.272.123 1.5 0 .365-.198.399-.352.581-1.034L5.003 1C4.007.993 3.21.989 2.5 1.5c-.71.504-1.054 1.487-1.523 2.475H0L.977 0H12v4l-1.25-.025c-.033-1.362-.398-2.292-1.095-2.79C9.35.968 8.5 1 8 1L5.17 11.435l-.167.726a1.738 1.738 0 0 0-.049.419c0 .36 0 .325.215.42.215.089 1.147-.048 1.831 0v1H0v-1Z' fill='%2355565B'/%3e%3c/svg%3e");
+  background-image: url("data:image/svg+xml,%3csvg height='36' viewBox='0 0 36 36' width='36' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m21 9.48v1.5h15v-3h-15zm-20.236 1.425c-.388 1.576-.705 2.913-.704 2.972 0 .097.062.105.698.09l.699-.017.459-.99c.567-1.222.894-1.777 1.322-2.241.813-.88 1.883-1.239 3.696-1.239h.579l-.04.165c-.449 1.841-4.58 17.152-4.673 17.32a1.696 1.696 0 0 1 -.362.42l-.231.185-1.104-.017-1.103-.016v1.443h10.5v-.716c0-.696-.004-.716-.135-.738-.074-.012-.676-.01-1.337.005-1.038.022-1.228.012-1.395-.074-.191-.099-.193-.105-.193-.542 0-.641.125-1.135 2.45-9.695l2.095-7.71.892.006c1.115.008 1.444.091 1.871.474.782.703 1.239 1.865 1.362 3.465l.041.525h1.849v-5.94h-16.532zm15.736 7.605v1.47h19.5v-2.94h-19.5zm-3 9v1.47h22.5v-2.94h-22.5z' fill='%2355565b'/%3e%3c/svg%3e");
 }
diff --git a/core/modules/text/tests/src/Functional/GenericTest.php b/core/modules/text/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..53c7aa05670a1b12e52d677bc00986867ba18a0b
--- /dev/null
+++ b/core/modules/text/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\text\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for text.
+ *
+ * @group text
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/text/text.field_type_categories.yml b/core/modules/text/text.field_type_categories.yml
index 787cfc9795727e2c21f9e09b920d249b0a86f89e..c819e4b83bc198057a4bf96fc9e1fa93df7979ce 100644
--- a/core/modules/text/text.field_type_categories.yml
+++ b/core/modules/text/text.field_type_categories.yml
@@ -2,3 +2,5 @@ formatted_text:
   label: 'Formatted text'
   description: 'Text field with markup support and optional editor.'
   weight: -45
+  libraries:
+    - text/drupal.text-icon
diff --git a/core/modules/text/text.module b/core/modules/text/text.module
index 54f513281d4fe7eda8aac5a51014754996e8668e..c0a03c62f97828740d0d500c3c061a94fc0446cb 100644
--- a/core/modules/text/text.module
+++ b/core/modules/text/text.module
@@ -164,10 +164,3 @@ function text_summary($text, $format = NULL, $size = NULL) {
 
   return $summary;
 }
-
-/**
- * Implements hook_preprocess_form_element__new_storage_type().
- */
-function text_preprocess_form_element__new_storage_type(&$variables) {
-  $variables['#attached']['library'][] = 'text/drupal.text-icon';
-}
diff --git a/core/modules/toolbar/css/toolbar.menu.css b/core/modules/toolbar/css/toolbar.menu.css
index e35d0adc2dfda05da6798f1a324660e93f19ad31..c5a08ed08e5b3ec809ba8dd25a454f8ff5c5e338 100644
--- a/core/modules/toolbar/css/toolbar.menu.css
+++ b/core/modules/toolbar/css/toolbar.menu.css
@@ -46,6 +46,7 @@
 .toolbar .toolbar-tray .menu-item--active-trail > .toolbar-box a,
 .toolbar .toolbar-tray a.is-active {
   color: #000;
+  background-color: #f5f5f5;
   font-weight: bold;
 }
 
diff --git a/core/modules/toolbar/js/models/MenuModel.js b/core/modules/toolbar/js/models/MenuModel.js
index 3d5f8352e114b2b34db1c067ea1107a10a908a62..19657c8bbd2e35999fe99bc2e9e9d462a14a0d38 100644
--- a/core/modules/toolbar/js/models/MenuModel.js
+++ b/core/modules/toolbar/js/models/MenuModel.js
@@ -16,13 +16,13 @@
       /**
        * @type {object}
        *
-       * @prop {object} subtrees
+       * @prop {object|null} subtrees
        */
       defaults: /** @lends Drupal.toolbar.MenuModel# */ {
         /**
-         * @type {object}
+         * @type {object|null}
          */
-        subtrees: {},
+        subtrees: null,
       },
     },
   );
diff --git a/core/modules/toolbar/js/toolbar.anti-flicker.js b/core/modules/toolbar/js/toolbar.anti-flicker.js
new file mode 100644
index 0000000000000000000000000000000000000000..43946ba8dc647e470811c7ce98dc2109654734f1
--- /dev/null
+++ b/core/modules/toolbar/js/toolbar.anti-flicker.js
@@ -0,0 +1,72 @@
+/**
+ * @file
+ * Prevents flicker of the toolbar on page load.
+ */
+
+(() => {
+  const toolbarState = sessionStorage.getItem('Drupal.toolbar.toolbarState')
+    ? JSON.parse(sessionStorage.getItem('Drupal.toolbar.toolbarState'))
+    : false;
+  // These are classes that toolbar typically adds to <body>, but this code
+  // executes before the first paint, when <body> is not yet present. The
+  // classes are added to <html> so styling immediately reflects the current
+  // toolbar state. The classes are removed after the toolbar completes
+  // initialization.
+  const classesToAdd = ['toolbar-loading', 'toolbar-anti-flicker'];
+  if (toolbarState) {
+    const {
+      orientation,
+      hasActiveTab,
+      isFixed,
+      activeTray,
+      activeTabId,
+      isOriented,
+      userButtonMinWidth,
+    } = toolbarState;
+
+    classesToAdd.push(
+      orientation ? `toolbar-${orientation}` : 'toolbar-horizontal',
+    );
+    if (hasActiveTab !== false) {
+      classesToAdd.push('toolbar-tray-open');
+    }
+    if (isFixed) {
+      classesToAdd.push('toolbar-fixed');
+    }
+    if (isOriented) {
+      classesToAdd.push('toolbar-oriented');
+    }
+
+    if (activeTray) {
+      // These styles are added so the active tab/tray styles are present
+      // immediately instead of "flickering" on as the toolbar initializes. In
+      // instances where a tray is lazy loaded, these styles facilitate the
+      // lazy loaded tray appearing gracefully and without reflow.
+      const styleContent = `
+      .toolbar-loading #${activeTabId} {
+        background-image: linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
+      }
+      .toolbar-loading #${activeTabId}-tray {
+        display: block; box-shadow: -1px 0 5px 2px rgb(0 0 0 / 33%);
+        border-right: 1px solid #aaa; background-color: #f5f5f5;
+        z-index: 0;
+      }
+      .toolbar-loading.toolbar-vertical.toolbar-tray-open #${activeTabId}-tray {
+        width: 15rem; height: 100vh;
+      }
+     .toolbar-loading.toolbar-horizontal :not(#${activeTray}) > .toolbar-lining {opacity: 0}`;
+
+      const style = document.createElement('style');
+      style.textContent = styleContent;
+      style.setAttribute('data-toolbar-anti-flicker-loading', true);
+      document.querySelector('head').appendChild(style);
+      if (userButtonMinWidth) {
+        const userButtonStyle = document.createElement('style');
+        userButtonStyle.textContent = `
+        #toolbar-item-user {min-width: ${userButtonMinWidth}.px;}`;
+        document.querySelector('head').appendChild(userButtonStyle);
+      }
+    }
+  }
+  document.querySelector('html').classList.add(...classesToAdd);
+})();
diff --git a/core/modules/toolbar/js/toolbar.menu.js b/core/modules/toolbar/js/toolbar.menu.js
index bde02e4be82e2ccecb030f5982733f8d81bd3ecc..bb2a5ff0905b958431fa3ccb06e3b9239b397a02 100644
--- a/core/modules/toolbar/js/toolbar.menu.js
+++ b/core/modules/toolbar/js/toolbar.menu.js
@@ -14,6 +14,33 @@
    */
   let activeItem = Drupal.url(drupalSettings.path.currentPath);
 
+  /**
+   * Maintains active tab in horizontal orientation.
+   */
+  $.fn.drupalToolbarMenuHorizontal = function () {
+    let currentPath = drupalSettings.path.currentPath;
+    const menu = once('toolbar-menu-horizontal', this);
+    if (menu.length) {
+      const $menu = $(menu);
+      if (activeItem) {
+        const count = currentPath.split('/').length;
+        // Find the deepest link with its parent info and start
+        // marking active.
+        for (let i = 0; i < count; i++) {
+          const $menuItem = $menu.find(
+            `a[data-drupal-link-system-path="${currentPath}"]`,
+          );
+          if ($menuItem.length !== 0) {
+            $menuItem.closest('a').addClass('is-active');
+            break;
+          }
+          const lastIndex = currentPath.lastIndexOf('/');
+          currentPath = currentPath.slice(0, lastIndex);
+        }
+      }
+    }
+  };
+
   $.fn.drupalToolbarMenu = function () {
     const ui = {
       handleOpen: Drupal.t('Extend'),
@@ -153,6 +180,7 @@
      *   The root of the menu.
      */
     function openActiveItem($menu) {
+      let currentPath = drupalSettings.path.currentPath;
       const pathItem = $menu.find(`a[href="${window.location.pathname}"]`);
       if (pathItem.length && !activeItem) {
         activeItem = window.location.pathname;
@@ -161,16 +189,36 @@
         const $activeItem = $menu
           .find(`a[href="${activeItem}"]`)
           .addClass('menu-item--active');
-        const $activeTrail = $activeItem
-          .parentsUntil('.root', 'li')
-          .addClass('menu-item--active-trail');
-        toggleList($activeTrail, true);
+        if (pathItem.length === 0 && activeItem) {
+          const count = currentPath.split('/').length;
+          // Find the deepest link with its parent info and start
+          // marking active.
+          for (let i = 0; i < count; i++) {
+            const $menuItem = $menu.find(
+              `a[data-drupal-link-system-path="${currentPath}"]`,
+            );
+            if ($menuItem.length !== 0) {
+              const $activeTrail = $menuItem
+                .parentsUntil('.root', 'li')
+                .addClass('menu-item--active-trail');
+              toggleList($activeTrail, true);
+              break;
+            }
+            const lastIndex = currentPath.lastIndexOf('/');
+            currentPath = currentPath.slice(0, lastIndex);
+          }
+        } else {
+          const $activeTrail = $activeItem
+            .parentsUntil('.root', 'li')
+            .addClass('menu-item--active-trail');
+          toggleList($activeTrail, true);
+        }
       }
     }
 
     // Return the jQuery object.
     return this.each(function (selector) {
-      const menu = once('toolbar-menu', this);
+      const menu = once('toolbar-menu-vertical', this);
       if (menu.length) {
         const $menu = $(menu);
         // Bind event handlers.
diff --git a/core/modules/toolbar/js/views/MenuVisualView.js b/core/modules/toolbar/js/views/MenuVisualView.js
index 916afb21a09cb732288c8dc5809380cc0a7bb37d..d230d4dfa7cf5b75a7359326e6e517f2ee366085 100644
--- a/core/modules/toolbar/js/views/MenuVisualView.js
+++ b/core/modules/toolbar/js/views/MenuVisualView.js
@@ -15,13 +15,40 @@
        */
       initialize() {
         this.listenTo(this.model, 'change:subtrees', this.render);
+
+        // Render the view immediately on initialization.
+        this.render();
       },
 
       /**
        * {@inheritdoc}
        */
       render() {
+        this.renderVertical();
+        this.renderHorizontal();
+      },
+
+      /**
+       * Renders the toolbar menu in horizontal mode.
+       */
+      renderHorizontal() {
+        // Render horizontal.
+        if ('drupalToolbarMenu' in $.fn) {
+          this.$el.children('.toolbar-menu').drupalToolbarMenuHorizontal();
+        }
+      },
+
+      /**
+       * Renders the toolbar menu in vertical mode.
+       */
+      renderVertical() {
         const subtrees = this.model.get('subtrees');
+
+        // Rendering the vertical menu depends on the subtrees.
+        if (!this.model.get('subtrees')) {
+          return;
+        }
+
         // Add subtrees.
         Object.keys(subtrees || {}).forEach((id) => {
           $(
diff --git a/core/modules/toolbar/tests/src/Functional/GenericTest.php b/core/modules/toolbar/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e0bdc71d8a2f8a63f048f41ec51d2d29571e0fa8
--- /dev/null
+++ b/core/modules/toolbar/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\toolbar\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for toolbar.
+ *
+ * @group toolbar
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/toolbar/tests/src/Functional/ToolbarAdminMenuTest.php b/core/modules/toolbar/tests/src/Functional/ToolbarAdminMenuTest.php
index 2d69089d64ed9ae7bc8c40648614db34919bb85e..135e2887d19b245da0860957a705e3a77b3e9e00 100644
--- a/core/modules/toolbar/tests/src/Functional/ToolbarAdminMenuTest.php
+++ b/core/modules/toolbar/tests/src/Functional/ToolbarAdminMenuTest.php
@@ -26,6 +26,7 @@
  * menu subtrees is compared to the new hash.
  *
  * @group toolbar
+ * @group #slow
  */
 class ToolbarAdminMenuTest extends BrowserTestBase {
 
@@ -65,6 +66,7 @@ class ToolbarAdminMenuTest extends BrowserTestBase {
     'language',
     'test_page_test',
     'locale',
+    'search',
   ];
 
   /**
@@ -95,6 +97,7 @@ protected function setUp(): void {
       'administer taxonomy',
       'administer languages',
       'translate interface',
+      'administer search',
     ];
 
     // Create an administrative user and log it in.
@@ -329,6 +332,8 @@ public function testLocaleTranslationSubtreesHashCacheClear() {
     // should create a new menu hash if the toolbar subtrees cache is correctly
     // invalidated.
     $this->drupalLogin($translate_user);
+    // We need to visit the page to get the string to be translated.
+    $this->drupalGet($langcode . '/admin/config');
     $search = [
       'string' => 'Search and metadata',
       'langcode' => $langcode,
@@ -358,6 +363,7 @@ public function testLocaleTranslationSubtreesHashCacheClear() {
     // of the link items in the Structure tree (Menus) has had its text
     // translated.
     $this->drupalLogin($admin_user);
+    $this->drupalGet('admin/config');
     // Have the adminUser request a page in the new language.
     $this->drupalGet($langcode . '/test-page');
     $this->assertSession()->statusCodeEquals(200);
diff --git a/core/modules/toolbar/tests/src/Functional/ToolbarMenuTranslationTest.php b/core/modules/toolbar/tests/src/Functional/ToolbarMenuTranslationTest.php
index 3079352f8e5d82b3bf958fa9faf6e86cec18f2c3..be99b93902c48ccb0e2fd6d7acd16f297a3be521 100644
--- a/core/modules/toolbar/tests/src/Functional/ToolbarMenuTranslationTest.php
+++ b/core/modules/toolbar/tests/src/Functional/ToolbarMenuTranslationTest.php
@@ -28,6 +28,7 @@ class ToolbarMenuTranslationTest extends BrowserTestBase {
     'toolbar_test',
     'locale',
     'locale_test',
+    'block',
   ];
 
   /**
@@ -47,6 +48,7 @@ protected function setUp(): void {
       'translate interface',
       'administer languages',
       'access administration pages',
+      'administer blocks',
     ]);
     $this->drupalLogin($this->adminUser);
   }
diff --git a/core/modules/toolbar/tests/src/FunctionalJavascript/ToolbarActiveTrailTest.php b/core/modules/toolbar/tests/src/FunctionalJavascript/ToolbarActiveTrailTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e6342ae13192640d311bc388f64a72adc5429130
--- /dev/null
+++ b/core/modules/toolbar/tests/src/FunctionalJavascript/ToolbarActiveTrailTest.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Drupal\Tests\toolbar\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+
+/**
+ * Tests that the active trail is maintained in the toolbar.
+ *
+ * @group toolbar
+ */
+class ToolbarActiveTrailTest extends WebDriverTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['toolbar', 'node', 'field_ui'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->drupalLogin($this->rootUser);
+    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
+  }
+
+  /**
+   * Tests that the active trail is maintained even when traversed deeper.
+   *
+   * @param string $orientation
+   *   The toolbar orientation.
+   *
+   * @testWith ["vertical"]
+   *           ["horizontal"]
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   */
+  public function testToolbarActiveTrail(string $orientation) {
+    $page = $this->getSession()->getPage();
+    $assert_session = $this->assertSession();
+
+    $this->drupalGet('<front>');
+    $this->assertNotEmpty($this->assertSession()->waitForElement('css', 'body.toolbar-horizontal'));
+    $this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '.toolbar-tray'));
+    $this->assertSession()->waitForElementRemoved('css', '.toolbar-loading');
+    $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#toolbar-item-administration.is-active'));
+
+    // If testing for vertical orientation of the toolbar then switch to it.
+    if ($orientation === 'vertical') {
+      $page->pressButton('Vertical orientation');
+    }
+
+    // Traverse deeper.
+    $this->clickLink('Structure');
+    $this->clickLink('Content types');
+    $this->clickLink('Manage fields');
+    $this->clickLink('Edit');
+
+    if ($orientation === 'vertical') {
+      $this->assertNotEmpty($assert_session->waitForElementVisible('named',
+        ['link', 'Structure']));
+      // Assert that menu-item--active-trail was maintained.
+      $this->assertTrue($assert_session->waitForElementVisible('named',
+        ['link', 'Structure'])->getParent()->getParent()->hasClass('menu-item--active-trail'));
+      $this->assertTrue($assert_session->waitForElementVisible('named',
+        ['link', 'Content types'])->getParent()->getParent()->hasClass('menu-item--active-trail'));
+      // Change orientation and check focus is maintained.
+      $page->pressButton('Horizontal orientation');
+      $this->assertTrue($assert_session->waitForElementVisible('css',
+        '#toolbar-link-system-admin_structure')->hasClass('is-active'));
+    }
+    else {
+      $this->assertNotEmpty($assert_session->waitForElementVisible('css', '#toolbar-link-system-admin_structure'));
+      // Assert that is-active was maintained.
+      $this->assertTrue($assert_session->waitForElementVisible('css', '#toolbar-link-system-admin_structure')->hasClass('is-active'));
+      // Change orientation and check focus is maintained.
+      $page->pressButton('Vertical orientation');
+      // Introduce a delay to let the focus load.
+      $this->getSession()->wait(150);
+      $this->assertTrue($assert_session->waitForElementVisible('named',
+        ['link', 'Structure'])->getParent()->getParent()->hasClass('menu-item--active-trail'));
+      $this->assertTrue($assert_session->waitForElementVisible('named',
+        ['link', 'Content types'])->getParent()->getParent()->hasClass('menu-item--active-trail'));
+    }
+  }
+
+}
diff --git a/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarApiTest.js b/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarApiTest.js
index 55f145bac969eb7ea0462b6b1ec9c79040ffe569..8e10c135a0117d869c26356d684ce9243613524e 100644
--- a/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarApiTest.js
+++ b/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarApiTest.js
@@ -8,8 +8,7 @@ module.exports = {
   before(browser) {
     browser
       .drupalInstall()
-      .drupalInstallModule('breakpoint')
-      .drupalInstallModule('toolbar')
+      .drupalInstallModule('toolbar', true)
       .drupalCreateUser({
         name: 'user',
         password: '123',
@@ -84,7 +83,7 @@ module.exports = {
         toReturn.toolbarModelOffsetsTop =
           models.toolbarModel.get('offsets').top === 79;
         toReturn.toolbarModelSubtrees =
-          Object.keys(models.menuModel.get('subtrees')).length === 0;
+          models.menuModel.get('subtrees') === null;
         return toReturn;
       },
       [],
diff --git a/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarTest.js b/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarTest.js
index e23488c900a8712c936e91f7e4168f45a9d11d67..a87b3982780fadb0b5388ea9f38e91d8a534495f 100644
--- a/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarTest.js
+++ b/core/modules/toolbar/tests/src/Nightwatch/Tests/toolbarTest.js
@@ -15,8 +15,7 @@ module.exports = {
   before(browser) {
     browser
       .drupalInstall()
-      .drupalInstallModule('breakpoint')
-      .drupalInstallModule('toolbar')
+      .drupalInstallModule('toolbar', true)
       .drupalCreateUser({
         name: 'user',
         password: '123',
@@ -99,23 +98,23 @@ module.exports = {
       'is-active toolbar-tray-vertical',
     );
     browser.waitForElementPresent(
-      '#toolbar-item-administration-tray li:nth-child(4) button',
+      '#toolbar-item-administration-tray li:nth-child(2) button',
     );
     browser.assert.not.hasClass(
-      '#toolbar-item-administration-tray li:nth-child(4)',
+      '#toolbar-item-administration-tray li:nth-child(2)',
       'open',
     );
     browser.assert.not.hasClass(
-      '#toolbar-item-administration-tray li:nth-child(4) button',
+      '#toolbar-item-administration-tray li:nth-child(2) button',
       'open',
     );
-    browser.click('#toolbar-item-administration-tray li:nth-child(4) button');
+    browser.click('#toolbar-item-administration-tray li:nth-child(2) button');
     browser.assert.hasClass(
-      '#toolbar-item-administration-tray li:nth-child(4)',
+      '#toolbar-item-administration-tray li:nth-child(2)',
       'open',
     );
     browser.assert.hasClass(
-      '#toolbar-item-administration-tray li:nth-child(4) button',
+      '#toolbar-item-administration-tray li:nth-child(2) button',
       'open',
     );
     browser.expect
diff --git a/core/modules/toolbar/toolbar.libraries.yml b/core/modules/toolbar/toolbar.libraries.yml
index 061bd7a98b4d527f4bdcedb2a4cf359b3656d34c..af520b8f89a972d3402259a97499015a18946c35 100644
--- a/core/modules/toolbar/toolbar.libraries.yml
+++ b/core/modules/toolbar/toolbar.libraries.yml
@@ -28,6 +28,7 @@ toolbar:
     - core/once
     - core/drupal.displace
     - toolbar/toolbar.menu
+    - toolbar/toolbar.anti-flicker
 
 toolbar.menu:
   version: VERSION
@@ -50,3 +51,9 @@ toolbar.escapeAdmin:
     - core/drupal
     - core/drupalSettings
     - core/once
+toolbar.anti-flicker:
+  # Block the page from being loaded until anti-flicker is initialized.
+  version: VERSION
+  header: true
+  js:
+    js/toolbar.anti-flicker.js: {}
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index e5ebb050393b219fc23adf4ca8bb3b3c323d7217..feeb1d4b2aa7df49ce9c390cbaeee2a8987b7bed 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -7,7 +7,6 @@
 
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Render\Element;
-use Drupal\Core\Render\Markup;
 use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Template\Attribute;
@@ -49,101 +48,6 @@ function toolbar_theme($existing, $type, $theme, $path) {
   return $items;
 }
 
-/**
- * Implements hook_page_attachments().
- */
-function toolbar_page_attachments(array &$page) {
-  // This JavaScript code provides temporary styles while the toolbar loads, so
-  // it better visually resembles the appearance it will have once fully loaded.
-  // @todo investigate potential alternatives to this approach in
-  //   https://www.drupal.org/i/3355381
-  $anti_flicker_js = <<<JS
-(function() {
-  const toolbarState = sessionStorage.getItem('Drupal.toolbar.toolbarState')
-    ? JSON.parse(sessionStorage.getItem('Drupal.toolbar.toolbarState'))
-    : false;
-  // These are classes that toolbar typically adds to <body>, but this code
-  // executes before the first paint, when <body> is not yet present. The
-  // classes are added to <html> so styling immediately reflects the current
-  // toolbar state. The classes are removed after the toolbar completes
-  // initialization.
-  const classesToAdd = ['toolbar-loading', 'toolbar-anti-flicker'];
-  if (toolbarState) {
-    const {
-      orientation,
-      hasActiveTab,
-      isFixed,
-      activeTray,
-      activeTabId,
-      isOriented,
-      userButtonMinWidth
-    } = toolbarState;
-
-    classesToAdd.push(
-      orientation ? `toolbar-` + orientation + `` : 'toolbar-horizontal',
-    );
-    if (hasActiveTab !== false) {
-      classesToAdd.push('toolbar-tray-open');
-    }
-    if (isFixed) {
-      classesToAdd.push('toolbar-fixed');
-    }
-    if (isOriented) {
-      classesToAdd.push('toolbar-oriented');
-    }
-
-    if (activeTray) {
-      // These styles are added so the active tab/tray styles are present
-      // immediately instead of "flickering" on as the toolbar initializes. In
-      // instances where a tray is lazy loaded, these styles facilitate the
-      // lazy loaded tray appearing gracefully and without reflow.
-      const styleContent = `
-      .toolbar-loading #` + activeTabId + ` {
-        background-image: linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
-      }
-      .toolbar-loading #` + activeTabId + `-tray {
-        display: block; box-shadow: -1px 0 5px 2px rgb(0 0 0 / 33%);
-        border-right: 1px solid #aaa; background-color: #f5f5f5;
-        z-index: 0;
-      }
-      .toolbar-loading.toolbar-vertical.toolbar-tray-open #` + activeTabId + `-tray {
-        width: 15rem; height: 100vh;
-      }
-     .toolbar-loading.toolbar-horizontal :not(#` + activeTray + `) > .toolbar-lining {opacity: 0}`;
-
-      const style = document.createElement('style');
-      style.textContent = styleContent;
-      style.setAttribute('data-toolbar-anti-flicker-loading', true);
-      document.querySelector('head').appendChild(style);
-
-      if (userButtonMinWidth) {
-        const userButtonStyle = document.createElement('style');
-        userButtonStyle.textContent = `#toolbar-item-user {min-width: ` + userButtonMinWidth +`px;}`
-        document.querySelector('head').appendChild(userButtonStyle);
-      }
-    }
-  }
-  document.querySelector('html').classList.add(...classesToAdd);
-})();
-JS;
-
-  // The anti flicker javascript is added as an inline tag so it is executed
-  // as early as possible. This enables it to add classes that prevent
-  // flickering before the first paint.
-  $page['#attached']['html_head'][] = [
-    [
-      '#tag' => 'script',
-      '#attributes' => [
-        'type' => 'text/javascript',
-        'data-toolbar-anti-flicker-loading' => TRUE,
-      ],
-      // Process through Markup to prevent character escaping.
-      '#value' => Markup::create($anti_flicker_js),
-    ],
-    'anti_flicker_js',
-  ];
-}
-
 /**
  * Implements hook_page_top().
  *
diff --git a/core/modules/tour/src/TourTipPluginInterface.php b/core/modules/tour/src/TourTipPluginInterface.php
index 27f5ac2d995d448e9d17cdb697b0c6a3a980bc1b..e486383e909942ea41b5b51fe1eac1a196399395 100644
--- a/core/modules/tour/src/TourTipPluginInterface.php
+++ b/core/modules/tour/src/TourTipPluginInterface.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\tour;
 
-@trigger_error('The ' . __NAMESPACE__ . '\TourTipPluginInterface is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Implement ' . __NAMESPACE__ . '\TipPluginInterface instead. See https://www.drupal.org/node/3340701.', E_USER_DEPRECATED);
+@trigger_error('The ' . __NAMESPACE__ . '\TourTipPluginInterface is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Implement ' . __NAMESPACE__ . '\TipPluginInterface instead. See https://www.drupal.org/node/3340701', E_USER_DEPRECATED);
 
 /**
  * Defines an interface for tour items.
diff --git a/core/modules/block/tests/src/Functional/BlockLayoutTourTest.php b/core/modules/tour/tests/src/Functional/Block/BlockLayoutTourTest.php
similarity index 93%
rename from core/modules/block/tests/src/Functional/BlockLayoutTourTest.php
rename to core/modules/tour/tests/src/Functional/Block/BlockLayoutTourTest.php
index 99cf79805f4b0a1a417a0f303ae6b2b0c60732b8..50bf112786a17dcd9caf6e27ea4337e6593f0bd5 100644
--- a/core/modules/block/tests/src/Functional/BlockLayoutTourTest.php
+++ b/core/modules/tour/tests/src/Functional/Block/BlockLayoutTourTest.php
@@ -1,13 +1,13 @@
 <?php
 
-namespace Drupal\Tests\block\Functional;
+namespace Drupal\Tests\tour\Functional\Block;
 
 use Drupal\Tests\tour\Functional\TourTestBase;
 
 /**
  * Tests the Block Layout tour.
  *
- * @group block
+ * @group tour
  */
 class BlockLayoutTourTest extends TourTestBase {
 
diff --git a/core/modules/tour/tests/src/Functional/GenericTest.php b/core/modules/tour/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7ba9dab5035a4da68cdd7eb55a3a8c717a0c63fb
--- /dev/null
+++ b/core/modules/tour/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\tour\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for tour.
+ *
+ * @group tour
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/jsonapi/tests/src/Functional/TourTest.php b/core/modules/tour/tests/src/Functional/Jsonapi/TourTest.php
similarity index 95%
rename from core/modules/jsonapi/tests/src/Functional/TourTest.php
rename to core/modules/tour/tests/src/Functional/Jsonapi/TourTest.php
index 138616099022124b765a62d33655a7e20389740a..3c0f37811a78fb953c35fa04a2b9369442879505 100644
--- a/core/modules/jsonapi/tests/src/Functional/TourTest.php
+++ b/core/modules/tour/tests/src/Functional/Jsonapi/TourTest.php
@@ -1,14 +1,15 @@
 <?php
 
-namespace Drupal\Tests\jsonapi\Functional;
+namespace Drupal\Tests\tour\Functional\Jsonapi;
 
 use Drupal\Core\Url;
+use Drupal\Tests\jsonapi\Functional\ConfigEntityResourceTestBase;
 use Drupal\tour\Entity\Tour;
 
 /**
  * JSON:API integration test for the "Tour" config entity type.
  *
- * @group jsonapi
+ * @group tour
  */
 class TourTest extends ConfigEntityResourceTestBase {
 
diff --git a/core/modules/language/tests/src/Functional/LanguageTourTest.php b/core/modules/tour/tests/src/Functional/Language/LanguageTourTest.php
similarity index 96%
rename from core/modules/language/tests/src/Functional/LanguageTourTest.php
rename to core/modules/tour/tests/src/Functional/Language/LanguageTourTest.php
index 002f6fdcbacdc7b3b65247161a685d06ea5827b0..b18d586da9f97d0393860621e613c7226458d178 100644
--- a/core/modules/language/tests/src/Functional/LanguageTourTest.php
+++ b/core/modules/tour/tests/src/Functional/Language/LanguageTourTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\Tests\language\Functional;
+namespace Drupal\Tests\tour\Functional\Language;
 
 use Drupal\Tests\tour\Functional\TourTestBase;
 
diff --git a/core/modules/locale/tests/src/Functional/LocaleTranslateStringTourTest.php b/core/modules/tour/tests/src/Functional/Locale/LocaleTranslateStringTourTest.php
similarity index 95%
rename from core/modules/locale/tests/src/Functional/LocaleTranslateStringTourTest.php
rename to core/modules/tour/tests/src/Functional/Locale/LocaleTranslateStringTourTest.php
index c4be9d779bc69eb8302f4d891b852e54a0c15e28..548a5ab869ebb6449b3cdbc0b86da302d54271ab 100644
--- a/core/modules/locale/tests/src/Functional/LocaleTranslateStringTourTest.php
+++ b/core/modules/tour/tests/src/Functional/Locale/LocaleTranslateStringTourTest.php
@@ -1,13 +1,13 @@
 <?php
 
-namespace Drupal\Tests\locale\Functional;
+namespace Drupal\Tests\tour\Functional\Locale;
 
 use Drupal\Tests\tour\Functional\TourTestBase;
 
 /**
  * Tests the Translate Interface tour.
  *
- * @group locale
+ * @group tour
  */
 class LocaleTranslateStringTourTest extends TourTestBase {
 
diff --git a/core/modules/views_ui/tests/src/Functional/ViewsUITourTest.php b/core/modules/tour/tests/src/Functional/ViewsUi/ViewsUITourTest.php
similarity index 97%
rename from core/modules/views_ui/tests/src/Functional/ViewsUITourTest.php
rename to core/modules/tour/tests/src/Functional/ViewsUi/ViewsUITourTest.php
index eea297a2b71ec86aa1aa74f438c1d505a931d732..1951a00877fb04b345389a3e2a137f5e0859f0af 100644
--- a/core/modules/views_ui/tests/src/Functional/ViewsUITourTest.php
+++ b/core/modules/tour/tests/src/Functional/ViewsUi/ViewsUITourTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\Tests\views_ui\Functional;
+namespace Drupal\Tests\tour\Functional\ViewsUi;
 
 use Drupal\Tests\tour\Functional\TourTestBase;
 use Drupal\language\Entity\ConfigurableLanguage;
@@ -8,7 +8,7 @@
 /**
  * Tests the Views UI tour.
  *
- * @group views_ui
+ * @group tour
  */
 class ViewsUITourTest extends TourTestBase {
 
diff --git a/core/modules/tour/tests/src/Kernel/TourTipLegacyTest.php b/core/modules/tour/tests/src/Kernel/TourTipLegacyTest.php
index 039d84e4410e90c4e41a9852f261bf5780b78376..41272c21c2d02f97276af548eb491cb5d864e661 100644
--- a/core/modules/tour/tests/src/Kernel/TourTipLegacyTest.php
+++ b/core/modules/tour/tests/src/Kernel/TourTipLegacyTest.php
@@ -15,7 +15,7 @@ class TourTipLegacyTest extends TestCase {
   use ExpectDeprecationTrait;
 
   public function testPluginHelperDeprecation(): void {
-    $this->expectDeprecation('The Drupal\tour\TourTipPluginInterface is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Implement Drupal\tour\TipPluginInterface instead. See https://www.drupal.org/node/3340701.');
+    $this->expectDeprecation('The Drupal\tour\TourTipPluginInterface is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Implement Drupal\tour\TipPluginInterface instead. See https://www.drupal.org/node/3340701');
     $plugin = $this->createMock(TourTipPluginInterface::class);
     $this->assertInstanceOf(TourTipPluginInterface::class, $plugin);
   }
diff --git a/core/modules/tracker/tests/src/Functional/GenericTest.php b/core/modules/tracker/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5305d4664e72e33adba09801cb9cb27aca1e1257
--- /dev/null
+++ b/core/modules/tracker/tests/src/Functional/GenericTest.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Drupal\Tests\tracker\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for tracker.
+ *
+ * @group tracker
+ * @group legacy
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/update/src/Controller/UpdateController.php b/core/modules/update/src/Controller/UpdateController.php
index 9873f894dc8f9c4065aaa3d1cd2289b532c70e30..481ee519dbd6f559fb1e235cd56d4ceea0e3fa48 100644
--- a/core/modules/update/src/Controller/UpdateController.php
+++ b/core/modules/update/src/Controller/UpdateController.php
@@ -5,9 +5,13 @@
 use Drupal\Core\Batch\BatchBuilder;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Routing\PathChangedHelper;
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\update\UpdateFetcherInterface;
 use Drupal\update\UpdateManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Controller routines for update routes.
@@ -97,4 +101,34 @@ public function updateStatusManually() {
     return batch_process('admin/reports/updates');
   }
 
+  /**
+   * Provides a redirect to update page.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   A route match object, used for the route name and the parameters.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request object.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse
+   *   Returns redirect.
+   *
+   * @deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use
+   *   /admin/appearance/update directly instead of /admin/theme/update.
+   *
+   * @see https://www.drupal.org/node/3375850
+   */
+  public function updateRedirect(RouteMatchInterface $route_match, Request $request): RedirectResponse {
+    @trigger_error('The path /admin/theme/update is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use /admin/appearance/update. See https://www.drupal.org/node/3382805', E_USER_DEPRECATED);
+    $helper = new PathChangedHelper($route_match, $request);
+    $params = [
+      '%old_path' => $helper->oldPath(),
+      '%new_path' => $helper->newPath(),
+      '%change_record' => 'https://www.drupal.org/node/3382805',
+    ];
+    $warning_message = $this->t('You have been redirected from %old_path. Update links, shortcuts, and bookmarks to use %new_path.', $params);
+    $this->messenger()->addWarning($warning_message);
+    $this->getLogger('update')->warning('A user was redirected from %old_path to %new_path. This redirect will be removed in a future version of Drupal. Update links, shortcuts, and bookmarks to use %new_path. See %change_record for more information.', $params);
+    return $helper->redirect();
+  }
+
 }
diff --git a/core/modules/update/src/UpdateSettingsForm.php b/core/modules/update/src/UpdateSettingsForm.php
index 1527b0a5a41d44f8692b64b90546e9e31aa0b4ca..80c726dc6730856786ed45027c5d5cded690327b 100644
--- a/core/modules/update/src/UpdateSettingsForm.php
+++ b/core/modules/update/src/UpdateSettingsForm.php
@@ -2,50 +2,18 @@
 
 namespace Drupal\update;
 
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
-use Drupal\Core\Url;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Config\Config;
 use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Url;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Component\Utility\EmailValidatorInterface;
 
 /**
  * Configure update settings for this site.
  *
  * @internal
  */
-class UpdateSettingsForm extends ConfigFormBase implements ContainerInjectionInterface {
-
-  /**
-   * The email validator.
-   *
-   * @var \Drupal\Component\Utility\EmailValidatorInterface
-   */
-  protected $emailValidator;
-
-  /**
-   * Constructs an UpdateSettingsForm object.
-   *
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
-   *   The factory for configuration objects.
-   * @param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
-   *   The email validator.
-   */
-  public function __construct(ConfigFactoryInterface $config_factory, EmailValidatorInterface $email_validator) {
-    parent::__construct($config_factory);
-    $this->emailValidator = $email_validator;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('config.factory'),
-      $container->get('email.validator')
-    );
-  }
+class UpdateSettingsForm extends ConfigFormBase {
 
   /**
    * {@inheritdoc}
@@ -105,7 +73,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         'You can choose to send email only if a security update is available, or to be notified about all newer versions. If there are updates available of Drupal core or any of your installed modules and themes, your site will always print a message on the <a href=":status_report">status report</a> page. If there is a security update, an error message will be printed on administration pages for users with <a href=":update_permissions">permission to view update notifications</a>.',
         [
           ':status_report' => Url::fromRoute('system.status')->toString(),
-          ':update_permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-update'])->toString(),
+          ':update_permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-update'])
+            ->toString(),
         ]
       ),
     ];
@@ -116,34 +85,56 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   /**
    * {@inheritdoc}
    */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-    $form_state->set('notify_emails', []);
-    if (!$form_state->isValueEmpty('update_notify_emails')) {
-      $valid = [];
-      $invalid = [];
-      foreach (explode("\n", trim($form_state->getValue('update_notify_emails'))) as $email) {
-        $email = trim($email);
-        if (!empty($email)) {
-          if ($this->emailValidator->isValid($email)) {
-            $valid[] = $email;
-          }
-          else {
-            $invalid[] = $email;
-          }
+  protected static function copyFormValuesToConfig(Config $config, FormStateInterface $form_state): void {
+    switch ($config->getName()) {
+      case 'update.settings':
+        $config
+          ->set('check.disabled_extensions', $form_state->getValue('update_check_disabled'))
+          ->set('check.interval_days', $form_state->getValue('update_check_frequency'))
+          ->set('notification.emails', array_map('trim', explode("\n", trim($form_state->getValue('update_notify_emails', '')))))
+          ->set('notification.threshold', $form_state->getValue('update_notification_threshold'));
+        break;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static function mapConfigKeyToFormElementName(string $config_name, string $key): string {
+    switch ($config_name) {
+      case 'update.settings':
+        // A `type: sequence` of emails is mapped to a single textarea. Property
+        // paths are `notification.emails.0`, `notification.emails.1`, etc.
+        if (str_starts_with($key, 'notification.emails.')) {
+          return 'update_notify_emails';
         }
-      }
-      if (empty($invalid)) {
-        $form_state->set('notify_emails', $valid);
-      }
-      elseif (count($invalid) == 1) {
-        $form_state->setErrorByName('update_notify_emails', $this->t('%email is not a valid email address.', ['%email' => reset($invalid)]));
-      }
-      else {
-        $form_state->setErrorByName('update_notify_emails', $this->t('%emails are not valid email addresses.', ['%emails' => implode(', ', $invalid)]));
-      }
+
+        return match ($key) {
+        'check.disabled_extensions' => 'update_check_disabled',
+          'check.interval_days' => 'update_check_frequency',
+          'notification.emails' => 'update_notify_emails',
+          'notification.threshold' => 'update_notification_threshold',
+          default => self::defaultMapConfigKeyToFormElementName($config_name, $key),
+        };
+
+        default:
+          throw new \InvalidArgumentException();
     }
+  }
 
-    parent::validateForm($form, $form_state);
+  /**
+   * {@inheritdoc}
+   */
+  protected function formatMultipleViolationsMessage(string $form_element_name, array $violations): TranslatableMarkup {
+    if ($form_element_name !== 'update_notify_emails') {
+      return parent::formatMultipleViolationsMessage($form_element_name, $violations);
+    }
+
+    $invalid_email_addresses = [];
+    foreach ($violations as $violation) {
+      $invalid_email_addresses[] = $violation->getInvalidValue();
+    }
+    return $this->t('%emails are not valid email addresses.', ['%emails' => implode(', ', $invalid_email_addresses)]);
   }
 
   /**
@@ -157,13 +148,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       update_storage_clear();
     }
 
-    $config
-      ->set('check.disabled_extensions', $form_state->getValue('update_check_disabled'))
-      ->set('check.interval_days', $form_state->getValue('update_check_frequency'))
-      ->set('notification.emails', $form_state->get('notify_emails'))
-      ->set('notification.threshold', $form_state->getValue('update_notification_threshold'))
-      ->save();
-
     parent::submitForm($form, $form_state);
   }
 
diff --git a/core/modules/update/tests/src/Functional/GenericTest.php b/core/modules/update/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..382db2bf779c19efe0aa5e6b4b4a4eef044fe83e
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for update.
+ *
+ * @group update
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/update/tests/src/Functional/UpdateContribTest.php b/core/modules/update/tests/src/Functional/UpdateContribTest.php
index 41f20ccff2ab22485fe2f3fb38e35d0e9ed9a51e..aa753e74684522d3820e9ff8a90fcaa40b48a3f5 100644
--- a/core/modules/update/tests/src/Functional/UpdateContribTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateContribTest.php
@@ -9,6 +9,7 @@
  * Tests how the Update Manager handles contributed modules and themes.
  *
  * @group update
+ * @group #slow
  */
 class UpdateContribTest extends UpdateTestBase {
   use UpdateTestTrait;
diff --git a/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php b/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php
index d3c16d0afe1bebc2c9a28917fcb5ef5467578839..b37eb3328448f5dc3a88d7961d8a037a84cc18e1 100644
--- a/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php
@@ -326,4 +326,15 @@ private function checkTableHeaders($table_locator, array $expected_headers) {
     }
   }
 
+  /**
+   * Tests the deprecation warnings.
+   *
+   * @group legacy
+   */
+  public function testDeprecationWarning() {
+    $this->drupalGet('admin/theme/update');
+    $this->expectDeprecation('The path /admin/theme/update is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use /admin/appearance/update. See https://www.drupal.org/node/3382805');
+    $this->assertSession()->statusMessageContains("You have been redirected from admin/theme/update. Update links, shortcuts, and bookmarks to use admin/appearance/update.", 'warning');
+  }
+
 }
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverContribBaselineTest.php b/core/modules/update/tests/src/Functional/UpdateSemverContribBaselineTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf0e48c5344d69435153b6cc2ad3aef9cf5e5bbd
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSemverContribBaselineTest.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Tests the Update Manager module with a contrib module with semver versions.
+ *
+ * @group update
+ * @group #slow
+ */
+class UpdateSemverContribBaselineTest extends UpdateSemverContribTestBase {
+
+  use UpdateSemverTestBaselineTrait;
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverContribSecurityAvailabilityTest.php b/core/modules/update/tests/src/Functional/UpdateSemverContribSecurityAvailabilityTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..821004fceb87d87451dc4d31b336e372f14da09d
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSemverContribSecurityAvailabilityTest.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Tests Update Manager with a security update available for a contrib project.
+ *
+ * @group update
+ * @group #slow
+ */
+class UpdateSemverContribSecurityAvailabilityTest extends UpdateSemverContribTestBase {
+
+  use UpdateSemverTestSecurityAvailabilityTrait;
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverContribTest.php b/core/modules/update/tests/src/Functional/UpdateSemverContribTestBase.php
similarity index 77%
rename from core/modules/update/tests/src/Functional/UpdateSemverContribTest.php
rename to core/modules/update/tests/src/Functional/UpdateSemverContribTestBase.php
index 213d6e1ce80b537bd7e704863a9f64d398ef9970..555cdec58fae03724f2a9809d650e94c7fe4952c 100644
--- a/core/modules/update/tests/src/Functional/UpdateSemverContribTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateSemverContribTestBase.php
@@ -3,12 +3,12 @@
 namespace Drupal\Tests\update\Functional;
 
 /**
- * Tests the Update Manager module with a contrib module with semver versions.
+ * Base class for Update manager semantic versioning tests of contrib projects.
  *
- * @group update
+ * This wires up the protected data from UpdateSemverTestBase for a contrib
+ * module with semantic version releases.
  */
-class UpdateSemverContribTest extends UpdateSemverTestBase {
-  use UpdateTestTrait;
+class UpdateSemverContribTestBase extends UpdateSemverTestBase {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverCoreBaselineTest.php b/core/modules/update/tests/src/Functional/UpdateSemverCoreBaselineTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9362198fed48c9384b6edd53678432270d4ae7c7
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSemverCoreBaselineTest.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Tests semantic version handling in the Update Manager for Drupal core.
+ *
+ * @group update
+ * @group #slow
+ */
+class UpdateSemverCoreBaselineTest extends UpdateSemverCoreTestBase {
+  use UpdateSemverTestBaselineTrait;
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverCoreSecurityAvailabilityTest.php b/core/modules/update/tests/src/Functional/UpdateSemverCoreSecurityAvailabilityTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..26eac5d1efd6a48c5ef55bc26b5c580752a55a90
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSemverCoreSecurityAvailabilityTest.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Tests Update Manager with a security update available for Drupal core.
+ *
+ * @group update
+ * @group #slow
+ */
+class UpdateSemverCoreSecurityAvailabilityTest extends UpdateSemverCoreTestBase {
+
+  use UpdateSemverTestSecurityAvailabilityTrait;
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverCoreSecurityCoverageTest.php b/core/modules/update/tests/src/Functional/UpdateSemverCoreSecurityCoverageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4b35c749904f304688f5145d29b5521c67226a51
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSemverCoreSecurityCoverageTest.php
@@ -0,0 +1,244 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Tests the security coverage messages for Drupal core versions.
+ *
+ * @group update
+ * @group #slow
+ */
+class UpdateSemverCoreSecurityCoverageTest extends UpdateSemverCoreTestBase {
+
+  /**
+   * Tests the security coverage messages for Drupal core versions.
+   *
+   * @param string $installed_version
+   *   The installed Drupal version to test.
+   * @param string $fixture
+   *   The test fixture that contains the test XML.
+   * @param string $requirements_section_heading
+   *   The requirements section heading.
+   * @param string $message
+   *   The expected coverage message.
+   * @param string $mock_date
+   *   The mock date to use if needed in the format CCYY-MM-DD. If an empty
+   *   string is provided, no mock date will be used.
+   *
+   * @dataProvider securityCoverageMessageProvider
+   */
+  public function testSecurityCoverageMessage($installed_version, $fixture, $requirements_section_heading, $message, $mock_date) {
+    \Drupal::state()->set('update_test.mock_date', $mock_date);
+    $this->setProjectInstalledVersion($installed_version);
+    $this->refreshUpdateStatus(['drupal' => $fixture]);
+    $this->drupalGet('admin/reports/status');
+
+    if (empty($requirements_section_heading)) {
+      $this->assertSession()->pageTextNotContains('Drupal core security coverage');
+      return;
+    }
+
+    $all_requirements_details = $this->getSession()->getPage()->findAll(
+      'css',
+      'details.system-status-report__entry:contains("Drupal core security coverage")'
+    );
+    // Ensure we only have 1 security message section.
+    $this->assertCount(1, $all_requirements_details);
+    $requirements_details = $all_requirements_details[0];
+    // Ensure that messages are under the correct heading which could be
+    // 'Checked', 'Warnings found', or 'Errors found'.
+    $requirements_section_element = $requirements_details->getParent();
+    $this->assertCount(1, $requirements_section_element->findAll('css', "h3:contains('$requirements_section_heading')"));
+    $actual_message = $requirements_details->find('css', 'div.system-status-report__entry__value')->getText();
+    $this->assertNotEmpty($actual_message);
+    $this->assertEquals($message, $actual_message);
+  }
+
+  /**
+   * Data provider for testSecurityCoverageMessage().
+   *
+   * These test cases rely on the following fixtures containing the following
+   * releases:
+   * - drupal.sec.2.0_3.0-rc1.xml
+   *   - 8.2.0
+   *   - 8.3.0-rc1
+   * - drupal.sec.2.0.xml
+   *   - 8.2.0
+   * - drupal.sec.2.0_9.0.0.xml
+   *   - 8.2.0
+   *   - 9.0.0
+   * - drupal.sec.9.5.0.xml
+   *   - 9.4.0
+   *   - 9.5.0
+   * - drupal.sec.10.5.0.xml
+   *   - 10.4.0
+   *   - 10.5.0
+   */
+  public function securityCoverageMessageProvider() {
+    $release_coverage_message = 'Visit the release cycle overview for more information on supported releases.';
+    $coverage_ended_message = 'Coverage has ended';
+    $update_asap_message = 'Update to a supported minor as soon as possible to continue receiving security updates.';
+    $update_soon_message = 'Update to a supported minor version soon to continue receiving security updates.';
+    $test_cases = [
+      '8.0.0, unsupported' => [
+        'installed_version' => '8.0.0',
+        'fixture' => 'sec.2.0_3.0-rc1',
+        'requirements_section_heading' => 'Errors found',
+        'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
+        'mock_date' => '',
+      ],
+      '8.1.0, supported with 3rc' => [
+        'installed_version' => '8.1.0',
+        'fixture' => 'sec.2.0_3.0-rc1',
+        'requirements_section_heading' => 'Warnings found',
+        'message' => "Covered until 8.3.0 Update to 8.2 or higher soon to continue receiving security updates. $release_coverage_message",
+        'mock_date' => '',
+      ],
+      '8.1.0, supported' => [
+        'installed_version' => '8.1.0',
+        'fixture' => 'sec.2.0',
+        'requirements_section_heading' => 'Warnings found',
+        'message' => "Covered until 8.3.0 Update to 8.2 or higher soon to continue receiving security updates. $release_coverage_message",
+        'mock_date' => '',
+      ],
+      '8.2.0, supported with 3rc' => [
+        'installed_version' => '8.2.0',
+        'fixture' => 'sec.2.0_3.0-rc1',
+        'requirements_section_heading' => 'Checked',
+        'message' => "Covered until 8.4.0 $release_coverage_message",
+        'mock_date' => '',
+      ],
+      '8.2.0, supported' => [
+        'installed_version' => '8.2.0',
+        'fixture' => 'sec.2.0',
+        'requirements_section_heading' => 'Checked',
+        'message' => "Covered until 8.4.0 $release_coverage_message",
+        'mock_date' => '',
+      ],
+      // Ensure we don't show messages for pre-release or dev versions.
+      '8.2.0-beta2, no message' => [
+        'installed_version' => '8.2.0-beta2',
+        'fixture' => 'sec.2.0_3.0-rc1',
+        'requirements_section_heading' => '',
+        'message' => '',
+        'mock_date' => '',
+      ],
+      '8.1.0-dev, no message' => [
+        'installed_version' => '8.1.0-dev',
+        'fixture' => 'sec.2.0_3.0-rc1',
+        'requirements_section_heading' => '',
+        'message' => '',
+        'mock_date' => '',
+      ],
+      // Ensures the message is correct if the next major version has been
+      // released and the additional minors indicated by
+      // CORE_MINORS_WITH_SECURITY_COVERAGE minors have been released.
+      '8.0.0, 9 unsupported' => [
+        'installed_version' => '8.0.0',
+        'fixture' => 'sec.2.0_9.0.0',
+        'requirements_section_heading' => 'Errors found',
+        'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
+        'mock_date' => '',
+      ],
+      // Ensures the message is correct if the next major version has been
+      // released and the additional minors indicated by
+      // CORE_MINORS_WITH_SECURITY_COVERAGE minors have not been released.
+      '8.2.0, 9 warning' => [
+        'installed_version' => '8.2.0',
+        'fixture' => 'sec.2.0_9.0.0',
+        'requirements_section_heading' => 'Warnings found',
+        'message' => "Covered until 8.4.0 Update to 8.3 or higher soon to continue receiving security updates. $release_coverage_message",
+        'mock_date' => '',
+      ],
+    ];
+
+    // Drupal 9.4.x test cases.
+    $test_cases += [
+      // Ensure that a message is displayed during 9.4's active support.
+      '9.4.0, supported' => [
+        'installed_version' => '9.4.0',
+        'fixture' => 'sec.9.5.0',
+        'requirements_section_heading' => 'Checked',
+        'message' => "Covered until 2023-Jun-21 $release_coverage_message",
+        'mock_date' => '2022-12-13',
+      ],
+      // Ensure a warning is displayed if less than six months remain until the
+      // end of 9.4's security coverage.
+      '9.4.0, supported, 6 months warn' => [
+        'installed_version' => '9.4.0',
+        'fixture' => 'sec.9.5.0',
+        'requirements_section_heading' => 'Warnings found',
+        'message' => "Covered until 2023-Jun-21 $update_soon_message $release_coverage_message",
+        'mock_date' => '2022-12-14',
+      ],
+    ];
+    // Ensure that the message does not change, including on the last day of
+    // security coverage.
+    $test_cases['9.4.0, supported, last day warn'] = $test_cases['9.4.0, supported, 6 months warn'];
+    $test_cases['9.4.0, supported, last day warn']['mock_date'] = '2023-06-20';
+
+    // Ensure that if the 9.4 support window is finished a message is
+    // displayed.
+    $test_cases['9.4.0, support over'] = [
+      'installed_version' => '9.4.0',
+      'fixture' => 'sec.9.5.0',
+      'requirements_section_heading' => 'Errors found',
+      'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
+      'mock_date' => '2023-06-22',
+    ];
+
+    // Drupal 9.5 test cases.
+    $test_cases['9.5.0, supported'] = [
+      'installed_version' => '9.5.0',
+      'fixture' => 'sec.9.5.0',
+      'requirements_section_heading' => 'Checked',
+      'message' => "Covered until 2023-Nov $release_coverage_message",
+      'mock_date' => '2023-01-01',
+    ];
+    // Ensure a warning is displayed if less than six months remain until the
+    // end of 9.5's security coverage.
+    $test_cases['9.5.0, supported, 6 months warn'] = [
+      'installed_version' => '9.5.0',
+      'fixture' => 'sec.9.5.0',
+      'requirements_section_heading' => 'Warnings found',
+      'message' => "Covered until 2023-Nov $update_soon_message $release_coverage_message",
+      'mock_date' => '2023-05-15',
+    ];
+
+    // Ensure that the message does not change, including on the last day of
+    // security coverage.
+    $test_cases['9.5.0, supported, last day warn'] = $test_cases['9.5.0, supported, 6 months warn'];
+    $test_cases['9.5.0, supported, last day warn']['mock_date'] = '2023-10-31';
+
+    // Ensure that if the support window is finished a message is displayed.
+    $test_cases['9.5.0, support over'] = [
+      'installed_version' => '9.5.0',
+      'fixture' => 'sec.9.5.0',
+      'requirements_section_heading' => 'Errors found',
+      'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
+      'mock_date' => '2023-11-01',
+    ];
+
+    // Drupal 9 test cases.
+    $test_cases += [
+      // Ensure the end dates for 9.4 and 9.5 only apply to major version 9.
+      '10.5.0' => [
+        'installed_version' => '10.5.0',
+        'fixture' => 'sec.10.5.0',
+        'requirements_section_heading' => 'Checked',
+        'message' => "Covered until 10.7.0 $release_coverage_message",
+        'mock_date' => '',
+      ],
+      '10.4.0' => [
+        'installed_version' => '10.4.0',
+        'fixture' => 'sec.10.5.0',
+        'requirements_section_heading' => 'Warnings found',
+        'message' => "Covered until 10.6.0 Update to 10.5 or higher soon to continue receiving security updates. $release_coverage_message",
+        'mock_date' => '',
+      ],
+    ];
+    return $test_cases;
+
+  }
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php b/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
index cd87ee90bc80bdbd11473c9acc859ea3a582ac36..4573cff6c0ccb468b46babee388b1ee1eb8c8fd0 100644
--- a/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
@@ -5,268 +5,15 @@
 use Drupal\Core\Url;
 
 /**
- * Tests the semantic version handling in the Update Manager.
+ * Tests edge cases of the Available Updates report UI.
+ *
+ * For example, manually checking for updates, recovering from problems
+ * connecting to the release history server, clearing the disk cache, and more.
  *
  * @group update
+ * @group #slow
  */
-class UpdateSemverCoreTest extends UpdateSemverTestBase {
-  use UpdateTestTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $updateTableLocator = 'table.update';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $updateProject = 'drupal';
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $projectTitle = 'Drupal';
-
-  /**
-   * Sets the version to x.x.x when no project-specific mapping is defined.
-   *
-   * @param string $version
-   *   The version.
-   */
-  protected function setProjectInstalledVersion($version) {
-    $this->mockDefaultExtensionsInfo(['version' => $version]);
-  }
-
-  /**
-   * Tests the security coverage messages for Drupal core versions.
-   *
-   * @param string $installed_version
-   *   The installed Drupal version to test.
-   * @param string $fixture
-   *   The test fixture that contains the test XML.
-   * @param string $requirements_section_heading
-   *   The requirements section heading.
-   * @param string $message
-   *   The expected coverage message.
-   * @param string $mock_date
-   *   The mock date to use if needed in the format CCYY-MM-DD. If an empty
-   *   string is provided, no mock date will be used.
-   *
-   * @dataProvider securityCoverageMessageProvider
-   */
-  public function testSecurityCoverageMessage($installed_version, $fixture, $requirements_section_heading, $message, $mock_date) {
-    \Drupal::state()->set('update_test.mock_date', $mock_date);
-    $this->setProjectInstalledVersion($installed_version);
-    $this->refreshUpdateStatus(['drupal' => $fixture]);
-    $this->drupalGet('admin/reports/status');
-
-    if (empty($requirements_section_heading)) {
-      $this->assertSession()->pageTextNotContains('Drupal core security coverage');
-      return;
-    }
-
-    $all_requirements_details = $this->getSession()->getPage()->findAll(
-      'css',
-      'details.system-status-report__entry:contains("Drupal core security coverage")'
-    );
-    // Ensure we only have 1 security message section.
-    $this->assertCount(1, $all_requirements_details);
-    $requirements_details = $all_requirements_details[0];
-    // Ensure that messages are under the correct heading which could be
-    // 'Checked', 'Warnings found', or 'Errors found'.
-    $requirements_section_element = $requirements_details->getParent();
-    $this->assertCount(1, $requirements_section_element->findAll('css', "h3:contains('$requirements_section_heading')"));
-    $actual_message = $requirements_details->find('css', 'div.system-status-report__entry__value')->getText();
-    $this->assertNotEmpty($actual_message);
-    $this->assertEquals($message, $actual_message);
-  }
-
-  /**
-   * Data provider for testSecurityCoverageMessage().
-   *
-   * These test cases rely on the following fixtures containing the following
-   * releases:
-   * - drupal.sec.2.0_3.0-rc1.xml
-   *   - 8.2.0
-   *   - 8.3.0-rc1
-   * - drupal.sec.2.0.xml
-   *   - 8.2.0
-   * - drupal.sec.2.0_9.0.0.xml
-   *   - 8.2.0
-   *   - 9.0.0
-   * - drupal.sec.9.5.0.xml
-   *   - 9.4.0
-   *   - 9.5.0
-   * - drupal.sec.10.5.0.xml
-   *   - 10.4.0
-   *   - 10.5.0
-   */
-  public function securityCoverageMessageProvider() {
-    $release_coverage_message = 'Visit the release cycle overview for more information on supported releases.';
-    $coverage_ended_message = 'Coverage has ended';
-    $update_asap_message = 'Update to a supported minor as soon as possible to continue receiving security updates.';
-    $update_soon_message = 'Update to a supported minor version soon to continue receiving security updates.';
-    $test_cases = [
-      '8.0.0, unsupported' => [
-        'installed_version' => '8.0.0',
-        'fixture' => 'sec.2.0_3.0-rc1',
-        'requirements_section_heading' => 'Errors found',
-        'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
-        'mock_date' => '',
-      ],
-      '8.1.0, supported with 3rc' => [
-        'installed_version' => '8.1.0',
-        'fixture' => 'sec.2.0_3.0-rc1',
-        'requirements_section_heading' => 'Warnings found',
-        'message' => "Covered until 8.3.0 Update to 8.2 or higher soon to continue receiving security updates. $release_coverage_message",
-        'mock_date' => '',
-      ],
-      '8.1.0, supported' => [
-        'installed_version' => '8.1.0',
-        'fixture' => 'sec.2.0',
-        'requirements_section_heading' => 'Warnings found',
-        'message' => "Covered until 8.3.0 Update to 8.2 or higher soon to continue receiving security updates. $release_coverage_message",
-        'mock_date' => '',
-      ],
-      '8.2.0, supported with 3rc' => [
-        'installed_version' => '8.2.0',
-        'fixture' => 'sec.2.0_3.0-rc1',
-        'requirements_section_heading' => 'Checked',
-        'message' => "Covered until 8.4.0 $release_coverage_message",
-        'mock_date' => '',
-      ],
-      '8.2.0, supported' => [
-        'installed_version' => '8.2.0',
-        'fixture' => 'sec.2.0',
-        'requirements_section_heading' => 'Checked',
-        'message' => "Covered until 8.4.0 $release_coverage_message",
-        'mock_date' => '',
-      ],
-      // Ensure we don't show messages for pre-release or dev versions.
-      '8.2.0-beta2, no message' => [
-        'installed_version' => '8.2.0-beta2',
-        'fixture' => 'sec.2.0_3.0-rc1',
-        'requirements_section_heading' => '',
-        'message' => '',
-        'mock_date' => '',
-      ],
-      '8.1.0-dev, no message' => [
-        'installed_version' => '8.1.0-dev',
-        'fixture' => 'sec.2.0_3.0-rc1',
-        'requirements_section_heading' => '',
-        'message' => '',
-        'mock_date' => '',
-      ],
-      // Ensures the message is correct if the next major version has been
-      // released and the additional minors indicated by
-      // CORE_MINORS_WITH_SECURITY_COVERAGE minors have been released.
-      '8.0.0, 9 unsupported' => [
-        'installed_version' => '8.0.0',
-        'fixture' => 'sec.2.0_9.0.0',
-        'requirements_section_heading' => 'Errors found',
-        'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
-        'mock_date' => '',
-      ],
-      // Ensures the message is correct if the next major version has been
-      // released and the additional minors indicated by
-      // CORE_MINORS_WITH_SECURITY_COVERAGE minors have not been released.
-      '8.2.0, 9 warning' => [
-        'installed_version' => '8.2.0',
-        'fixture' => 'sec.2.0_9.0.0',
-        'requirements_section_heading' => 'Warnings found',
-        'message' => "Covered until 8.4.0 Update to 8.3 or higher soon to continue receiving security updates. $release_coverage_message",
-        'mock_date' => '',
-      ],
-    ];
-
-    // Drupal 9.4.x test cases.
-    $test_cases += [
-      // Ensure that a message is displayed during 9.4's active support.
-      '9.4.0, supported' => [
-        'installed_version' => '9.4.0',
-        'fixture' => 'sec.9.5.0',
-        'requirements_section_heading' => 'Checked',
-        'message' => "Covered until 2023-Jun-21 $release_coverage_message",
-        'mock_date' => '2022-12-13',
-      ],
-      // Ensure a warning is displayed if less than six months remain until the
-      // end of 9.4's security coverage.
-      '9.4.0, supported, 6 months warn' => [
-        'installed_version' => '9.4.0',
-        'fixture' => 'sec.9.5.0',
-        'requirements_section_heading' => 'Warnings found',
-        'message' => "Covered until 2023-Jun-21 $update_soon_message $release_coverage_message",
-        'mock_date' => '2022-12-14',
-      ],
-    ];
-    // Ensure that the message does not change, including on the last day of
-    // security coverage.
-    $test_cases['9.4.0, supported, last day warn'] = $test_cases['9.4.0, supported, 6 months warn'];
-    $test_cases['9.4.0, supported, last day warn']['mock_date'] = '2023-06-20';
-
-    // Ensure that if the 9.4 support window is finished a message is
-    // displayed.
-    $test_cases['9.4.0, support over'] = [
-      'installed_version' => '9.4.0',
-      'fixture' => 'sec.9.5.0',
-      'requirements_section_heading' => 'Errors found',
-      'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
-      'mock_date' => '2023-06-22',
-    ];
-
-    // Drupal 9.5 test cases.
-    $test_cases['9.5.0, supported'] = [
-      'installed_version' => '9.5.0',
-      'fixture' => 'sec.9.5.0',
-      'requirements_section_heading' => 'Checked',
-      'message' => "Covered until 2023-Nov $release_coverage_message",
-      'mock_date' => '2023-01-01',
-    ];
-    // Ensure a warning is displayed if less than six months remain until the
-    // end of 9.5's security coverage.
-    $test_cases['9.5.0, supported, 6 months warn'] = [
-      'installed_version' => '9.5.0',
-      'fixture' => 'sec.9.5.0',
-      'requirements_section_heading' => 'Warnings found',
-      'message' => "Covered until 2023-Nov $update_soon_message $release_coverage_message",
-      'mock_date' => '2023-05-15',
-    ];
-
-    // Ensure that the message does not change, including on the last day of
-    // security coverage.
-    $test_cases['9.5.0, supported, last day warn'] = $test_cases['9.5.0, supported, 6 months warn'];
-    $test_cases['9.5.0, supported, last day warn']['mock_date'] = '2023-10-31';
-
-    // Ensure that if the support window is finished a message is displayed.
-    $test_cases['9.5.0, support over'] = [
-      'installed_version' => '9.5.0',
-      'fixture' => 'sec.9.5.0',
-      'requirements_section_heading' => 'Errors found',
-      'message' => "$coverage_ended_message $update_asap_message $release_coverage_message",
-      'mock_date' => '2023-11-01',
-    ];
-
-    // Drupal 9 test cases.
-    $test_cases += [
-      // Ensure the end dates for 9.4 and 9.5 only apply to major version 9.
-      '10.5.0' => [
-        'installed_version' => '10.5.0',
-        'fixture' => 'sec.10.5.0',
-        'requirements_section_heading' => 'Checked',
-        'message' => "Covered until 10.7.0 $release_coverage_message",
-        'mock_date' => '',
-      ],
-      '10.4.0' => [
-        'installed_version' => '10.4.0',
-        'fixture' => 'sec.10.5.0',
-        'requirements_section_heading' => 'Warnings found',
-        'message' => "Covered until 10.6.0 Update to 10.5 or higher soon to continue receiving security updates. $release_coverage_message",
-        'mock_date' => '',
-      ],
-    ];
-    return $test_cases;
-
-  }
+class UpdateSemverCoreTest extends UpdateSemverCoreTestBase {
 
   /**
    * Ensures proper results where there are date mismatches among modules.
@@ -432,25 +179,25 @@ public function testServiceUnavailable() {
    * Tests that exactly one fetch task per project is created and not more.
    */
   public function testFetchTasks() {
-    $projecta = [
+    $project_a = [
       'name' => 'aaa_update_test',
     ];
-    $projectb = [
+    $project_b = [
       'name' => 'bbb_update_test',
     ];
     $queue = \Drupal::queue('update_fetch_tasks');
     $this->assertEquals(0, $queue->numberOfItems(), 'Queue is empty');
-    update_create_fetch_task($projecta);
+    update_create_fetch_task($project_a);
     $this->assertEquals(1, $queue->numberOfItems(), 'Queue contains one item');
-    update_create_fetch_task($projectb);
+    update_create_fetch_task($project_b);
     $this->assertEquals(2, $queue->numberOfItems(), 'Queue contains two items');
     // Try to add a project again.
-    update_create_fetch_task($projecta);
+    update_create_fetch_task($project_a);
     $this->assertEquals(2, $queue->numberOfItems(), 'Queue still contains two items');
 
     // Clear storage and try again.
     update_storage_clear();
-    update_create_fetch_task($projecta);
+    update_create_fetch_task($project_a);
     $this->assertEquals(2, $queue->numberOfItems(), 'Queue contains two items');
   }
 
@@ -523,7 +270,7 @@ public function testBrokenThenFixedUpdates() {
     \Drupal::keyValueExpirable('update_available_releases')->deleteAll();
     // This cron run should retrieve fixed updates.
     $this->cronRun();
-    $this->drupalGet('admin/structure');
+    $this->drupalGet('admin/config');
     $this->assertSession()->statusCodeEquals(200);
     $this->assertSession()->pageTextContains('There is a security update available for your version of Drupal.');
   }
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverCoreTestBase.php b/core/modules/update/tests/src/Functional/UpdateSemverCoreTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..32afae2bcc40d08628286eaa392b265a2afad9a4
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSemverCoreTestBase.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Base class for Update manager semantic versioning tests of Drupal core.
+ *
+ * This wires up the protected data from UpdateSemverTestBase for Drupal core
+ * with semantic version releases.
+ */
+class UpdateSemverCoreTestBase extends UpdateSemverTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $updateTableLocator = 'table.update';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $updateProject = 'drupal';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $projectTitle = 'Drupal';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setProjectInstalledVersion($version) {
+    $this->mockDefaultExtensionsInfo(['version' => $version]);
+  }
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverTestBase.php b/core/modules/update/tests/src/Functional/UpdateSemverTestBase.php
index d5e102dd4c705e67fe073952ee7f960d826d7a7b..666c7d897748fba31f7129735acbfd9727f7764b 100644
--- a/core/modules/update/tests/src/Functional/UpdateSemverTestBase.php
+++ b/core/modules/update/tests/src/Functional/UpdateSemverTestBase.php
@@ -2,14 +2,12 @@
 
 namespace Drupal\Tests\update\Functional;
 
-use Drupal\Core\Link;
-use Drupal\Core\Url;
 use Drupal\Tests\Traits\Core\CronRunTrait;
 
 /**
- * Common test methods for projects that use semver version releases.
+ * Common setup and utility methods to test projects that use semver releases.
  *
- * For classes that extend this class, the XML fixtures they will start with
+ * For classes that extend this class, the XML fixtures they use will start with
  * ::$projectTitle.
  *
  * @group update
@@ -17,6 +15,7 @@
 abstract class UpdateSemverTestBase extends UpdateTestBase {
 
   use CronRunTrait;
+  use UpdateTestTrait;
 
   /**
    * Modules to enable.
@@ -50,383 +49,6 @@ protected function setUp(): void {
     $this->drupalPlaceBlock('local_actions_block');
   }
 
-  /**
-   * Tests the Update Manager module when no updates are available.
-   *
-   * The XML fixture file 'drupal.1.0.xml' which is one of the XML files this
-   * test uses also contains 2 extra releases that are newer than '8.0.1'. These
-   * releases will not show as available updates because of the following
-   * reasons:
-   * - '8.0.2' is an unpublished release.
-   * - '8.0.3' is marked as 'Release type' 'Unsupported'.
-   */
-  public function testNoUpdatesAvailable() {
-    foreach ([0, 1] as $minor_version) {
-      foreach ([0, 1] as $patch_version) {
-        foreach (['-alpha1', '-beta1', ''] as $extra_version) {
-          $this->setProjectInstalledVersion("8.$minor_version.$patch_version" . $extra_version);
-          $this->refreshUpdateStatus([$this->updateProject => "$minor_version.$patch_version" . $extra_version]);
-          $this->standardTests();
-          // The XML test fixtures for this method all contain the '8.2.0'
-          // release but because '8.2.0' is not in a supported branch it will
-          // not be in the available updates.
-          $this->assertUpdateTableElementNotContains('8.2.0');
-          $this->assertUpdateTableTextContains('Up to date');
-          $this->assertUpdateTableTextNotContains('Update available');
-          $this->assertUpdateTableTextNotContains('Security update required!');
-          $this->assertUpdateTableElementContains('check.svg');
-        }
-      }
-    }
-  }
-
-  /**
-   * Tests the Update Manager module when one normal update is available.
-   */
-  public function testNormalUpdateAvailable() {
-    $this->setProjectInstalledVersion('8.0.0');
-
-    // Ensure that the update check requires a token.
-    $this->drupalGet('admin/reports/updates/check');
-    $this->assertSession()->statusCodeEquals(403);
-
-    foreach ([0, 1] as $minor_version) {
-      foreach (['-alpha1', '-beta1', ''] as $extra_version) {
-        $full_version = "8.$minor_version.1$extra_version";
-        $this->refreshUpdateStatus([$this->updateProject => "$minor_version.1" . $extra_version]);
-        $this->standardTests();
-        $this->assertUpdateTableTextNotContains('Security update required!');
-        // The XML test fixtures for this method all contain the '8.2.0' release
-        // but because '8.2.0' is not in a supported branch it will not be in
-        // the available updates.
-        $this->assertSession()->responseNotContains('8.2.0');
-        switch ($minor_version) {
-          case 0:
-            // Both stable and unstable releases are available.
-            // A stable release is the latest.
-            if ($extra_version == '') {
-              $this->assertUpdateTableTextNotContains('Up to date');
-              $this->assertUpdateTableTextContains('Update available');
-              $this->assertVersionUpdateLinks('Recommended version:', $full_version);
-              $this->assertUpdateTableTextNotContains('Latest version:');
-              $this->assertUpdateTableElementContains('warning.svg');
-            }
-            // Only unstable releases are available.
-            // An unstable release is the latest.
-            else {
-              $this->assertUpdateTableTextContains('Up to date');
-              $this->assertUpdateTableTextNotContains('Update available');
-              $this->assertUpdateTableTextNotContains('Recommended version:');
-              $this->assertVersionUpdateLinks('Latest version:', $full_version);
-              $this->assertUpdateTableElementContains('check.svg');
-            }
-            break;
-
-          case 1:
-            // Both stable and unstable releases are available.
-            // A stable release is the latest.
-            if ($extra_version == '') {
-              $this->assertUpdateTableTextNotContains('Up to date');
-              $this->assertUpdateTableTextContains('Update available');
-              $this->assertVersionUpdateLinks('Recommended version:', $full_version);
-              $this->assertUpdateTableTextNotContains('Latest version:');
-              $this->assertUpdateTableElementContains('warning.svg');
-            }
-            // Both stable and unstable releases are available.
-            // An unstable release is the latest.
-            else {
-              $this->assertUpdateTableTextNotContains('Up to date');
-              $this->assertUpdateTableTextContains('Update available');
-              $this->assertVersionUpdateLinks('Recommended version:', '8.1.0');
-              $this->assertVersionUpdateLinks('Latest version:', $full_version);
-              $this->assertUpdateTableElementContains('warning.svg');
-            }
-            break;
-        }
-      }
-    }
-  }
-
-  /**
-   * Tests the Update Manager module when a major update is available.
-   */
-  public function testMajorUpdateAvailable() {
-    foreach ([0, 1] as $minor_version) {
-      foreach ([0, 1] as $patch_version) {
-        foreach (['-alpha1', '-beta1', ''] as $extra_version) {
-          $this->setProjectInstalledVersion("8.$minor_version.$patch_version" . $extra_version);
-          $this->refreshUpdateStatus([$this->updateProject => '9']);
-          $this->standardTests();
-          $this->assertUpdateTableTextNotContains('Security update required!');
-          $this->assertUpdateTableElementContains(Link::fromTextAndUrl('9.0.0', Url::fromUri("http://example.com/{$this->updateProject}-9-0-0-release"))->toString());
-          $this->assertUpdateTableElementContains(Link::fromTextAndUrl('Release notes', Url::fromUri("http://example.com/{$this->updateProject}-9-0-0-release"))->toString());
-          $this->assertUpdateTableTextNotContains('Up to date');
-          $this->assertUpdateTableTextContains('Not supported!');
-          $this->assertUpdateTableTextContains('Recommended version:');
-          $this->assertUpdateTableTextNotContains('Latest version:');
-          $this->assertUpdateTableElementContains('error.svg');
-        }
-      }
-    }
-  }
-
-  /**
-   * Tests the Update Manager module when a security update is available.
-   *
-   * @param string $site_patch_version
-   *   The patch version to set the site to for testing.
-   * @param string[] $expected_security_releases
-   *   The security releases, if any, that the status report should recommend.
-   * @param string $expected_update_message_type
-   *   The type of update message expected.
-   * @param string $fixture
-   *   The test fixture that contains the test XML.
-   *
-   * @dataProvider securityUpdateAvailabilityProvider
-   */
-  public function testSecurityUpdateAvailability($site_patch_version, array $expected_security_releases, $expected_update_message_type, $fixture) {
-    $this->setProjectInstalledVersion("8.$site_patch_version");
-    $this->refreshUpdateStatus([$this->updateProject => $fixture]);
-    $this->assertSecurityUpdates("{$this->updateProject}-8", $expected_security_releases, $expected_update_message_type, $this->updateTableLocator);
-  }
-
-  /**
-   * Data provider method for testSecurityUpdateAvailability().
-   *
-   * These test cases rely on the following fixtures containing the following
-   * releases:
-   * - [::$updateProject].sec.0.1_0.2.xml
-   *   - 8.0.2 Security update
-   *   - 8.0.1 Security update, Insecure
-   *   - 8.0.0 Insecure
-   * - [::$updateProject].sec.0.2.xml
-   *   - 8.0.2 Security update
-   *   - 8.0.1 Insecure
-   *   - 8.0.0 Insecure
-   * - [::$updateProject].sec.2.0-rc2.xml
-   *   - 8.2.0-rc2 Security update
-   *   - 8.2.0-rc1 Insecure
-   *   - 8.2.0-beta2 Insecure
-   *   - 8.2.0-beta1 Insecure
-   *   - 8.2.0-alpha2 Insecure
-   *   - 8.2.0-alpha1 Insecure
-   *   - 8.1.2 Security update
-   *   - 8.1.1 Insecure
-   *   - 8.1.0 Insecure
-   *   - 8.0.2 Security update
-   *   - 8.0.1 Insecure
-   *   - 8.0.0 Insecure
-   * - [::$updateProject].sec.1.2.xml
-   *   - 8.1.2 Security update
-   *   - 8.1.1 Insecure
-   *   - 8.1.0 Insecure
-   *   - 8.0.2
-   *   - 8.0.1
-   *   - 8.0.0
-   * - [::$updateProject].sec.1.2_insecure.xml
-   *   - 8.1.2 Security update
-   *   - 8.1.1 Insecure
-   *   - 8.1.0 Insecure
-   *   - 8.0.2 Insecure
-   *   - 8.0.1 Insecure
-   *   - 8.0.0 Insecure
-   * - [::$updateProject].sec.1.2_insecure-unsupported
-   *   This file has the exact releases as
-   *   [::$updateProject].sec.1.2_insecure.xml. It has a different value for
-   *   'supported_branches' that does not contain '8.0.'. It is used to ensure
-   *   that the "Security update required!" is displayed even if the currently
-   *   installed version is in an unsupported branch.
-   * - [::$updateProject].sec.2.0-rc2-b.xml
-   *   - 8.2.0-rc2
-   *   - 8.2.0-rc1
-   *   - 8.2.0-beta2
-   *   - 8.2.0-beta1
-   *   - 8.2.0-alpha2
-   *   - 8.2.0-alpha1
-   *   - 8.1.2 Security update
-   *   - 8.1.1 Insecure
-   *   - 8.1.0 Insecure
-   *   - 8.0.2 Security update
-   *   - 8.0.1 Insecure
-   *   - 8.0.0 Insecure
-   */
-  public function securityUpdateAvailabilityProvider() {
-    $test_cases = [
-      // Security release available for site minor release 0.
-      // No releases for next minor.
-      '0.0, 0.2' => [
-        'site_patch_version' => '0.0',
-        'expected_security_releases' => ['0.2'],
-        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
-        'fixture' => 'sec.0.2',
-      ],
-      // Site on latest security release available for site minor release 0.
-      // Minor release 1 also has a security release, and the current release
-      // is marked as insecure.
-      '0.2, 0.2' => [
-        'site_patch_version' => '0.2',
-        'expected_security_release' => ['1.2', '2.0-rc2'],
-        'expected_update_message_type' => static::UPDATE_AVAILABLE,
-        'fixture' => 'sec.2.0-rc2',
-      ],
-      // Two security releases available for site minor release 0.
-      // 0.1 security release marked as insecure.
-      // No releases for next minor.
-      '0.0, 0.1 0.2' => [
-        'site_patch_version' => '0.0',
-        'expected_security_releases' => ['0.2'],
-        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
-        'fixture' => 'sec.0.1_0.2',
-      ],
-      // Security release available for site minor release 1.
-      // No releases for next minor.
-      '1.0, 1.2' => [
-        'site_patch_version' => '1.0',
-        'expected_security_releases' => ['1.2'],
-        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
-        'fixture' => 'sec.1.2',
-      ],
-      // Security release available for site minor release 0.
-      // Security release also available for next minor.
-      '0.0, 0.2 1.2' => [
-        'site_patch_version' => '0.0',
-        'expected_security_releases' => ['0.2', '1.2', '2.0-rc2'],
-        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
-        'fixture' => 'sec.2.0-rc2',
-      ],
-      // No newer security release for site minor 1.
-      // Previous minor has security release.
-      '1.2, 0.2 1.2' => [
-        'site_patch_version' => '1.2',
-        'expected_security_releases' => [],
-        'expected_update_message_type' => static::UPDATE_NONE,
-        'fixture' => 'sec.2.0-rc2',
-      ],
-      // No security release available for site minor release 0.
-      // Security release available for next minor.
-      '0.0, 1.2, insecure' => [
-        'site_patch_version' => '0.0',
-        'expected_security_releases' => ['1.2'],
-        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
-        'fixture' => 'sec.1.2_insecure',
-      ],
-      // No security release available for site minor release 0.
-      // Site minor is not a supported branch.
-      // Security release available for next minor.
-      '0.0, 1.2, insecure-unsupported' => [
-        'site_patch_version' => '0.0',
-        'expected_security_releases' => ['1.2'],
-        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
-        'fixture' => 'sec.1.2_insecure-unsupported',
-      ],
-      // All releases for minor 0 are secure.
-      // Security release available for next minor.
-      '0.0, 1.2, secure' => [
-        'site_patch_version' => '0.0',
-        'expected_security_release' => ['1.2'],
-        'expected_update_message_type' => static::UPDATE_AVAILABLE,
-        'fixture' => 'sec.1.2',
-      ],
-      '0.2, 1.2, secure' => [
-        'site_patch_version' => '0.2',
-        'expected_security_release' => ['1.2'],
-        'expected_update_message_type' => static::UPDATE_AVAILABLE,
-        'fixture' => 'sec.1.2',
-      ],
-      // Site on 2.0-rc2 which is a security release.
-      '2.0-rc2, 0.2 1.2' => [
-        'site_patch_version' => '2.0-rc2',
-        'expected_security_releases' => [],
-        'expected_update_message_type' => static::UPDATE_NONE,
-        'fixture' => 'sec.2.0-rc2',
-      ],
-      // Ensure that 8.0.2 security release is not shown because it is earlier
-      // version than 1.0.
-      '1.0, 0.2 1.2' => [
-        'site_patch_version' => '1.0',
-        'expected_security_releases' => ['1.2', '2.0-rc2'],
-        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
-        'fixture' => 'sec.2.0-rc2',
-      ],
-    ];
-    $pre_releases = [
-      '2.0-alpha1',
-      '2.0-alpha2',
-      '2.0-beta1',
-      '2.0-beta2',
-      '2.0-rc1',
-      '2.0-rc2',
-    ];
-
-    foreach ($pre_releases as $pre_release) {
-      // If the site is on an alpha/beta/RC of an upcoming minor and none of the
-      // alpha/beta/RC versions are marked insecure, no security update should
-      // be required.
-      $test_cases["Pre-release:$pre_release, no security update"] = [
-        'site_patch_version' => $pre_release,
-        'expected_security_releases' => [],
-        'expected_update_message_type' => $pre_release === '2.0-rc2' ? static::UPDATE_NONE : static::UPDATE_AVAILABLE,
-        'fixture' => 'sec.2.0-rc2-b',
-      ];
-      // If the site is on an alpha/beta/RC of an upcoming minor and there is
-      // an RC version with a security update, it should be recommended.
-      $test_cases["Pre-release:$pre_release, security update"] = [
-        'site_patch_version' => $pre_release,
-        'expected_security_releases' => $pre_release === '2.0-rc2' ? [] : ['2.0-rc2'],
-        'expected_update_message_type' => $pre_release === '2.0-rc2' ? static::UPDATE_NONE : static::SECURITY_UPDATE_REQUIRED,
-        'fixture' => 'sec.2.0-rc2',
-      ];
-    }
-    return $test_cases;
-  }
-
-  /**
-   * Tests messages when a project release is unpublished.
-   *
-   * This test confirms that revoked messages are displayed regardless of
-   * whether the installed version is in a supported branch or not. This test
-   * relies on 2 test XML fixtures that are identical except for the
-   * 'supported_branches' value:
-   * - [::$updateProject].1.0.xml
-   *    'supported_branches' is '8.0.,8.1.'.
-   * - [::$updateProject].1.0-unsupported.xml
-   *    'supported_branches' is '8.1.'.
-   * They both have an '8.0.2' release that is unpublished and an '8.1.0'
-   * release that is published and is the expected update.
-   */
-  public function testRevokedRelease() {
-    foreach (['1.0', '1.0-unsupported'] as $fixture) {
-      $this->setProjectInstalledVersion('8.0.2');
-      $this->refreshUpdateStatus([$this->updateProject => $fixture]);
-      $this->standardTests();
-      $this->confirmRevokedStatus('8.0.2', '8.1.0', 'Recommended version:');
-    }
-  }
-
-  /**
-   * Tests messages when a project release is marked unsupported.
-   *
-   * This test confirms unsupported messages are displayed regardless of whether
-   * the installed version is in a supported branch or not. This test relies on
-   * 2 test XML fixtures that are identical except for the 'supported_branches'
-   * value:
-   * - [::$updateProject].1.0.xml
-   *    'supported_branches' is '8.0.,8.1.'.
-   * - [::$updateProject].1.0-unsupported.xml
-   *    'supported_branches' is '8.1.'.
-   * They both have an '8.0.3' release that has the 'Release type' value of
-   * 'unsupported' and an '8.1.0' release that has the 'Release type' value of
-   * 'supported' and is the expected update.
-   */
-  public function testUnsupportedRelease() {
-    foreach (['1.0', '1.0-unsupported'] as $fixture) {
-      $this->setProjectInstalledVersion('8.0.3');
-      $this->refreshUpdateStatus([$this->updateProject => $fixture]);
-      $this->standardTests();
-      $this->confirmUnsupportedStatus('8.0.3', '8.1.0', 'Recommended version:');
-    }
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverTestBaselineTrait.php b/core/modules/update/tests/src/Functional/UpdateSemverTestBaselineTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..b9b86fd6a451d2b3d3d26709819bd7b22882a476
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSemverTestBaselineTrait.php
@@ -0,0 +1,183 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+
+/**
+ * Provides test methods for semver tests shared between core and contrib.
+ *
+ * All of this "baseline" semver behavior should be the same for both Drupal
+ * core and contributed projects that use semantic versioning.
+ */
+trait UpdateSemverTestBaselineTrait {
+
+  /**
+   * Tests the Update Manager module when no updates are available.
+   *
+   * The XML fixture file 'drupal.1.0.xml' which is one of the XML files this
+   * test uses also contains 2 extra releases that are newer than '8.0.1'. These
+   * releases will not show as available updates because of the following
+   * reasons:
+   * - '8.0.2' is an unpublished release.
+   * - '8.0.3' is marked as 'Release type' 'Unsupported'.
+   */
+  public function testNoUpdatesAvailable() {
+    foreach ([0, 1] as $minor_version) {
+      foreach ([0, 1] as $patch_version) {
+        foreach (['-alpha1', '-beta1', ''] as $extra_version) {
+          $this->setProjectInstalledVersion("8.$minor_version.$patch_version" . $extra_version);
+          $this->refreshUpdateStatus([$this->updateProject => "$minor_version.$patch_version" . $extra_version]);
+          $this->standardTests();
+          // The XML test fixtures for this method all contain the '8.2.0'
+          // release but because '8.2.0' is not in a supported branch it will
+          // not be in the available updates.
+          $this->assertUpdateTableElementNotContains('8.2.0');
+          $this->assertUpdateTableTextContains('Up to date');
+          $this->assertUpdateTableTextNotContains('Update available');
+          $this->assertUpdateTableTextNotContains('Security update required!');
+          $this->assertUpdateTableElementContains('check.svg');
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests the Update Manager module when one normal update is available.
+   */
+  public function testNormalUpdateAvailable() {
+    $this->setProjectInstalledVersion('8.0.0');
+
+    // Ensure that the update check requires a token.
+    $this->drupalGet('admin/reports/updates/check');
+    $this->assertSession()->statusCodeEquals(403);
+
+    foreach ([0, 1] as $minor_version) {
+      foreach (['-alpha1', '-beta1', ''] as $extra_version) {
+        $full_version = "8.$minor_version.1$extra_version";
+        $this->refreshUpdateStatus([$this->updateProject => "$minor_version.1" . $extra_version]);
+        $this->standardTests();
+        $this->assertUpdateTableTextNotContains('Security update required!');
+        // The XML test fixtures for this method all contain the '8.2.0' release
+        // but because '8.2.0' is not in a supported branch it will not be in
+        // the available updates.
+        $this->assertSession()->responseNotContains('8.2.0');
+        switch ($minor_version) {
+          case 0:
+            // Both stable and unstable releases are available.
+            // A stable release is the latest.
+            if ($extra_version == '') {
+              $this->assertUpdateTableTextNotContains('Up to date');
+              $this->assertUpdateTableTextContains('Update available');
+              $this->assertVersionUpdateLinks('Recommended version:', $full_version);
+              $this->assertUpdateTableTextNotContains('Latest version:');
+              $this->assertUpdateTableElementContains('warning.svg');
+            }
+            // Only unstable releases are available.
+            // An unstable release is the latest.
+            else {
+              $this->assertUpdateTableTextContains('Up to date');
+              $this->assertUpdateTableTextNotContains('Update available');
+              $this->assertUpdateTableTextNotContains('Recommended version:');
+              $this->assertVersionUpdateLinks('Latest version:', $full_version);
+              $this->assertUpdateTableElementContains('check.svg');
+            }
+            break;
+
+          case 1:
+            // Both stable and unstable releases are available.
+            // A stable release is the latest.
+            if ($extra_version == '') {
+              $this->assertUpdateTableTextNotContains('Up to date');
+              $this->assertUpdateTableTextContains('Update available');
+              $this->assertVersionUpdateLinks('Recommended version:', $full_version);
+              $this->assertUpdateTableTextNotContains('Latest version:');
+              $this->assertUpdateTableElementContains('warning.svg');
+            }
+            // Both stable and unstable releases are available.
+            // An unstable release is the latest.
+            else {
+              $this->assertUpdateTableTextNotContains('Up to date');
+              $this->assertUpdateTableTextContains('Update available');
+              $this->assertVersionUpdateLinks('Recommended version:', '8.1.0');
+              $this->assertVersionUpdateLinks('Latest version:', $full_version);
+              $this->assertUpdateTableElementContains('warning.svg');
+            }
+            break;
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests the Update Manager module when a major update is available.
+   */
+  public function testMajorUpdateAvailable() {
+    foreach ([0, 1] as $minor_version) {
+      foreach ([0, 1] as $patch_version) {
+        foreach (['-alpha1', '-beta1', ''] as $extra_version) {
+          $this->setProjectInstalledVersion("8.$minor_version.$patch_version" . $extra_version);
+          $this->refreshUpdateStatus([$this->updateProject => '9']);
+          $this->standardTests();
+          $this->assertUpdateTableTextNotContains('Security update required!');
+          $this->assertUpdateTableElementContains(Link::fromTextAndUrl('9.0.0', Url::fromUri("http://example.com/{$this->updateProject}-9-0-0-release"))->toString());
+          $this->assertUpdateTableElementContains(Link::fromTextAndUrl('Release notes', Url::fromUri("http://example.com/{$this->updateProject}-9-0-0-release"))->toString());
+          $this->assertUpdateTableTextNotContains('Up to date');
+          $this->assertUpdateTableTextContains('Not supported!');
+          $this->assertUpdateTableTextContains('Recommended version:');
+          $this->assertUpdateTableTextNotContains('Latest version:');
+          $this->assertUpdateTableElementContains('error.svg');
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests messages when a project release is unpublished.
+   *
+   * This test confirms that revoked messages are displayed regardless of
+   * whether the installed version is in a supported branch or not. This test
+   * relies on 2 test XML fixtures that are identical except for the
+   * 'supported_branches' value:
+   * - [::$updateProject].1.0.xml
+   *    'supported_branches' is '8.0.,8.1.'.
+   * - [::$updateProject].1.0-unsupported.xml
+   *    'supported_branches' is '8.1.'.
+   * They both have an '8.0.2' release that is unpublished and an '8.1.0'
+   * release that is published and is the expected update.
+   */
+  public function testRevokedRelease() {
+    foreach (['1.0', '1.0-unsupported'] as $fixture) {
+      $this->setProjectInstalledVersion('8.0.2');
+      $this->refreshUpdateStatus([$this->updateProject => $fixture]);
+      $this->standardTests();
+      $this->confirmRevokedStatus('8.0.2', '8.1.0', 'Recommended version:');
+    }
+  }
+
+  /**
+   * Tests messages when a project release is marked unsupported.
+   *
+   * This test confirms unsupported messages are displayed regardless of whether
+   * the installed version is in a supported branch or not. This test relies on
+   * 2 test XML fixtures that are identical except for the 'supported_branches'
+   * value:
+   * - [::$updateProject].1.0.xml
+   *    'supported_branches' is '8.0.,8.1.'.
+   * - [::$updateProject].1.0-unsupported.xml
+   *    'supported_branches' is '8.1.'.
+   * They both have an '8.0.3' release that has the 'Release type' value of
+   * 'unsupported' and an '8.1.0' release that has the 'Release type' value of
+   * 'supported' and is the expected update.
+   */
+  public function testUnsupportedRelease() {
+    foreach (['1.0', '1.0-unsupported'] as $fixture) {
+      $this->setProjectInstalledVersion('8.0.3');
+      $this->refreshUpdateStatus([$this->updateProject => $fixture]);
+      $this->standardTests();
+      $this->confirmUnsupportedStatus('8.0.3', '8.1.0', 'Recommended version:');
+    }
+  }
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverTestSecurityAvailabilityTrait.php b/core/modules/update/tests/src/Functional/UpdateSemverTestSecurityAvailabilityTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..3925847b7b8c1607064a13dfcf8ea1837449d384
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSemverTestSecurityAvailabilityTrait.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Provides a test and data provider for semver security availability tests.
+ */
+trait UpdateSemverTestSecurityAvailabilityTrait {
+
+  /**
+   * Tests the Update Manager module when a security update is available.
+   *
+   * @param string $site_patch_version
+   *   The patch version to set the site to for testing.
+   * @param string[] $expected_security_releases
+   *   The security releases, if any, that the status report should recommend.
+   * @param string $expected_update_message_type
+   *   The type of update message expected.
+   * @param string $fixture
+   *   The test fixture that contains the test XML.
+   *
+   * @dataProvider securityUpdateAvailabilityProvider
+   */
+  public function testSecurityUpdateAvailability($site_patch_version, array $expected_security_releases, $expected_update_message_type, $fixture) {
+    $this->setProjectInstalledVersion("8.$site_patch_version");
+    $this->refreshUpdateStatus([$this->updateProject => $fixture]);
+    $this->assertSecurityUpdates("{$this->updateProject}-8", $expected_security_releases, $expected_update_message_type, $this->updateTableLocator);
+  }
+
+  /**
+   * Data provider method for testSecurityUpdateAvailability().
+   *
+   * These test cases rely on the following fixtures containing the following
+   * releases:
+   * - [::$updateProject].sec.0.1_0.2.xml
+   *   - 8.0.2 Security update
+   *   - 8.0.1 Security update, Insecure
+   *   - 8.0.0 Insecure
+   * - [::$updateProject].sec.0.2.xml
+   *   - 8.0.2 Security update
+   *   - 8.0.1 Insecure
+   *   - 8.0.0 Insecure
+   * - [::$updateProject].sec.2.0-rc2.xml
+   *   - 8.2.0-rc2 Security update
+   *   - 8.2.0-rc1 Insecure
+   *   - 8.2.0-beta2 Insecure
+   *   - 8.2.0-beta1 Insecure
+   *   - 8.2.0-alpha2 Insecure
+   *   - 8.2.0-alpha1 Insecure
+   *   - 8.1.2 Security update
+   *   - 8.1.1 Insecure
+   *   - 8.1.0 Insecure
+   *   - 8.0.2 Security update
+   *   - 8.0.1 Insecure
+   *   - 8.0.0 Insecure
+   * - [::$updateProject].sec.1.2.xml
+   *   - 8.1.2 Security update
+   *   - 8.1.1 Insecure
+   *   - 8.1.0 Insecure
+   *   - 8.0.2
+   *   - 8.0.1
+   *   - 8.0.0
+   * - [::$updateProject].sec.1.2_insecure.xml
+   *   - 8.1.2 Security update
+   *   - 8.1.1 Insecure
+   *   - 8.1.0 Insecure
+   *   - 8.0.2 Insecure
+   *   - 8.0.1 Insecure
+   *   - 8.0.0 Insecure
+   * - [::$updateProject].sec.1.2_insecure-unsupported
+   *   This file has the exact releases as
+   *   [::$updateProject].sec.1.2_insecure.xml. It has a different value for
+   *   'supported_branches' that does not contain '8.0.'. It is used to ensure
+   *   that the "Security update required!" is displayed even if the currently
+   *   installed version is in an unsupported branch.
+   * - [::$updateProject].sec.2.0-rc2-b.xml
+   *   - 8.2.0-rc2
+   *   - 8.2.0-rc1
+   *   - 8.2.0-beta2
+   *   - 8.2.0-beta1
+   *   - 8.2.0-alpha2
+   *   - 8.2.0-alpha1
+   *   - 8.1.2 Security update
+   *   - 8.1.1 Insecure
+   *   - 8.1.0 Insecure
+   *   - 8.0.2 Security update
+   *   - 8.0.1 Insecure
+   *   - 8.0.0 Insecure
+   */
+  public function securityUpdateAvailabilityProvider() {
+    $test_cases = [
+      // Security release available for site minor release 0.
+      // No releases for next minor.
+      '0.0, 0.2' => [
+        'site_patch_version' => '0.0',
+        'expected_security_releases' => ['0.2'],
+        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
+        'fixture' => 'sec.0.2',
+      ],
+      // Site on latest security release available for site minor release 0.
+      // Minor release 1 also has a security release, and the current release
+      // is marked as insecure.
+      '0.2, 0.2' => [
+        'site_patch_version' => '0.2',
+        'expected_security_release' => ['1.2', '2.0-rc2'],
+        'expected_update_message_type' => static::UPDATE_AVAILABLE,
+        'fixture' => 'sec.2.0-rc2',
+      ],
+      // Two security releases available for site minor release 0.
+      // 0.1 security release marked as insecure.
+      // No releases for next minor.
+      '0.0, 0.1 0.2' => [
+        'site_patch_version' => '0.0',
+        'expected_security_releases' => ['0.2'],
+        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
+        'fixture' => 'sec.0.1_0.2',
+      ],
+      // Security release available for site minor release 1.
+      // No releases for next minor.
+      '1.0, 1.2' => [
+        'site_patch_version' => '1.0',
+        'expected_security_releases' => ['1.2'],
+        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
+        'fixture' => 'sec.1.2',
+      ],
+      // Security release available for site minor release 0.
+      // Security release also available for next minor.
+      '0.0, 0.2 1.2' => [
+        'site_patch_version' => '0.0',
+        'expected_security_releases' => ['0.2', '1.2', '2.0-rc2'],
+        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
+        'fixture' => 'sec.2.0-rc2',
+      ],
+      // No newer security release for site minor 1.
+      // Previous minor has security release.
+      '1.2, 0.2 1.2' => [
+        'site_patch_version' => '1.2',
+        'expected_security_releases' => [],
+        'expected_update_message_type' => static::UPDATE_NONE,
+        'fixture' => 'sec.2.0-rc2',
+      ],
+      // No security release available for site minor release 0.
+      // Security release available for next minor.
+      '0.0, 1.2, insecure' => [
+        'site_patch_version' => '0.0',
+        'expected_security_releases' => ['1.2'],
+        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
+        'fixture' => 'sec.1.2_insecure',
+      ],
+      // No security release available for site minor release 0.
+      // Site minor is not a supported branch.
+      // Security release available for next minor.
+      '0.0, 1.2, insecure-unsupported' => [
+        'site_patch_version' => '0.0',
+        'expected_security_releases' => ['1.2'],
+        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
+        'fixture' => 'sec.1.2_insecure-unsupported',
+      ],
+      // All releases for minor 0 are secure.
+      // Security release available for next minor.
+      '0.0, 1.2, secure' => [
+        'site_patch_version' => '0.0',
+        'expected_security_release' => ['1.2'],
+        'expected_update_message_type' => static::UPDATE_AVAILABLE,
+        'fixture' => 'sec.1.2',
+      ],
+      '0.2, 1.2, secure' => [
+        'site_patch_version' => '0.2',
+        'expected_security_release' => ['1.2'],
+        'expected_update_message_type' => static::UPDATE_AVAILABLE,
+        'fixture' => 'sec.1.2',
+      ],
+      // Site on 2.0-rc2 which is a security release.
+      '2.0-rc2, 0.2 1.2' => [
+        'site_patch_version' => '2.0-rc2',
+        'expected_security_releases' => [],
+        'expected_update_message_type' => static::UPDATE_NONE,
+        'fixture' => 'sec.2.0-rc2',
+      ],
+      // Ensure that 8.0.2 security release is not shown because it is earlier
+      // version than 1.0.
+      '1.0, 0.2 1.2' => [
+        'site_patch_version' => '1.0',
+        'expected_security_releases' => ['1.2', '2.0-rc2'],
+        'expected_update_message_type' => static::SECURITY_UPDATE_REQUIRED,
+        'fixture' => 'sec.2.0-rc2',
+      ],
+    ];
+    $pre_releases = [
+      '2.0-alpha1',
+      '2.0-alpha2',
+      '2.0-beta1',
+      '2.0-beta2',
+      '2.0-rc1',
+      '2.0-rc2',
+    ];
+
+    foreach ($pre_releases as $pre_release) {
+      // If the site is on an alpha/beta/RC of an upcoming minor and none of the
+      // alpha/beta/RC versions are marked insecure, no security update should
+      // be required.
+      $test_cases["Pre-release:$pre_release, no security update"] = [
+        'site_patch_version' => $pre_release,
+        'expected_security_releases' => [],
+        'expected_update_message_type' => $pre_release === '2.0-rc2' ? static::UPDATE_NONE : static::UPDATE_AVAILABLE,
+        'fixture' => 'sec.2.0-rc2-b',
+      ];
+      // If the site is on an alpha/beta/RC of an upcoming minor and there is
+      // an RC version with a security update, it should be recommended.
+      $test_cases["Pre-release:$pre_release, security update"] = [
+        'site_patch_version' => $pre_release,
+        'expected_security_releases' => $pre_release === '2.0-rc2' ? [] : ['2.0-rc2'],
+        'expected_update_message_type' => $pre_release === '2.0-rc2' ? static::UPDATE_NONE : static::SECURITY_UPDATE_REQUIRED,
+        'fixture' => 'sec.2.0-rc2',
+      ];
+    }
+    return $test_cases;
+  }
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php b/core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f2554e77f43b5af5a756625b235a795e21d71ad
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateSettingsFormTest.php
@@ -0,0 +1,97 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\update\Functional;
+
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests the the update_settings form.
+ *
+ * @group update
+ * @group Form
+ */
+class UpdateSettingsFormTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['update'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Tests the update_settings form.
+   */
+  public function testUpdateSettingsForm() {
+    $url = Url::fromRoute('update.settings');
+
+    // Users without the appropriate permissions should not be able to access.
+    $this->drupalGet($url);
+    $this->assertSession()->pageTextContains('Access denied');
+
+    // Users with permission should be able to access the form.
+    $permissions = ['administer site configuration'];
+    $account = $this->setUpCurrentUser([
+      'name' => 'system_admin',
+      'pass' => 'adminPass',
+    ], $permissions);
+    $this->drupalLogin($account);
+    $this->drupalGet($url);
+    $this->assertSession()->fieldExists('update_notify_emails');
+
+    $values_to_enter = [
+      'http://example.com',
+      'sofie@example.com',
+      'http://example.com/also-not-an-email-address',
+      'dries@example.com',
+    ];
+
+    // Fill in `http://example.com` as the email address to notify. We expect
+    // this to trigger a validation error, because it's not an email address,
+    // and for the corresponding form item to be highlighted.
+    $this->assertSession()->fieldExists('update_notify_emails')->setValue($values_to_enter[0]);
+    $this->submitForm([], 'Save configuration');
+    $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_STATUS);
+    $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_WARNING);
+    $this->assertSession()->statusMessageContains('"http://example.com" is not a valid email address.', MessengerInterface::TYPE_ERROR);
+    $this->assertTrue($this->assertSession()->fieldExists('update_notify_emails')->hasClass('error'));
+    $this->assertSame([], $this->config('update.settings')->get('notification.emails'));
+
+    // Next, set an invalid email addresses, but make sure it's second entry.
+    $this->assertSession()->fieldExists('update_notify_emails')->setValue(implode("\n", array_slice($values_to_enter, 1, 2)));
+    $this->submitForm([], 'Save configuration');
+    $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_STATUS);
+    $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_WARNING);
+    $this->assertSession()->statusMessageContains('"http://example.com/also-not-an-email-address" is not a valid email address.', MessengerInterface::TYPE_ERROR);
+    $this->assertTrue($this->assertSession()->fieldExists('update_notify_emails')->hasClass('error'));
+    $this->assertSame([], $this->config('update.settings')->get('notification.emails'));
+
+    // Next, set multiple invalid email addresses, and assert the same as above
+    // except the message should be adjusted now.
+    $this->assertSession()->fieldExists('update_notify_emails')->setValue(implode("\n", $values_to_enter));
+    $this->submitForm([], 'Save configuration');
+    $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_STATUS);
+    $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_WARNING);
+    $this->assertSession()->statusMessageContains('http://example.com, http://example.com/also-not-an-email-address are not valid email addresses.', MessengerInterface::TYPE_ERROR);
+    $this->assertTrue($this->assertSession()->fieldExists('update_notify_emails')->hasClass('error'));
+    $this->assertSame([], $this->config('update.settings')->get('notification.emails'));
+
+    // Now fill in valid email addresses, now the form should be saved
+    // successfully.
+    $this->assertSession()->fieldExists('update_notify_emails')->setValue("$values_to_enter[1]\r\n$values_to_enter[3]");
+    $this->submitForm([], 'Save configuration');
+    $this->assertSession()->statusMessageContains('The configuration options have been saved.', MessengerInterface::TYPE_STATUS);
+    $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_WARNING);
+    $this->assertSession()->statusMessageNotExists(MessengerInterface::TYPE_ERROR);
+    $this->assertFalse($this->assertSession()->fieldExists('update_notify_emails')->hasClass('error'));
+    $this->assertSame(['sofie@example.com', 'dries@example.com'], $this->config('update.settings')->get('notification.emails'));
+  }
+
+}
diff --git a/core/modules/update/update.routing.yml b/core/modules/update/update.routing.yml
index 790a08d4212fc6e86aecdf3859626c2fe52bdf73..b4502d1e5633645835cbc72784f838c4b82d0655 100644
--- a/core/modules/update/update.routing.yml
+++ b/core/modules/update/update.routing.yml
@@ -69,7 +69,7 @@ update.theme_install:
     _access_update_manager: 'TRUE'
 
 update.theme_update:
-  path: '/admin/theme/update'
+  path: '/admin/appearance/update'
   defaults:
     _form: '\Drupal\update\Form\UpdateManagerUpdate'
     _title: 'Update'
@@ -77,6 +77,19 @@ update.theme_update:
     _permission: 'administer software updates'
     _access_update_manager: 'TRUE'
 
+# @todo Deprecate this route once
+#   https://www.drupal.org/project/drupal/issues/3159210 is fixed, or remove
+#   it in Drupal 11.
+# @see https://www.drupal.org/node/3375850
+update.theme_update.bc:
+  path: '/admin/theme/update'
+  defaults:
+    _title: 'Update'
+    _controller: '\Drupal\update\Controller\UpdateController::updateRedirect'
+  requirements:
+    _permission: 'administer software updates'
+    _access_update_manager: 'TRUE'
+
 update.confirmation_page:
   path: '/admin/update/ready'
   defaults:
diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php
index 6b4afbeb8b40275b03974c3fa97fcda1f75be182..f059e063744260e4bd0d124dc0b5ea658818c74e 100644
--- a/core/modules/user/src/AccountSettingsForm.php
+++ b/core/modules/user/src/AccountSettingsForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\user;
 
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -36,13 +37,15 @@ class AccountSettingsForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
    * @param \Drupal\user\RoleStorageInterface $role_storage
    *   The role storage.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, RoleStorageInterface $role_storage) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler, RoleStorageInterface $role_storage) {
+    parent::__construct($config_factory, $typedConfigManager);
     $this->moduleHandler = $module_handler;
     $this->roleStorage = $role_storage;
   }
@@ -53,6 +56,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ModuleHandle
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('module_handler'),
       $container->get('entity_type.manager')->getStorage('user_role')
     );
diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php
index ae8d6c5eb82380787a0f922fc07330679557ac65..357004a80427dcbaeaf2c7f80547bf35337e717e 100644
--- a/core/modules/user/src/Controller/UserController.php
+++ b/core/modules/user/src/Controller/UserController.php
@@ -133,7 +133,8 @@ public function resetPass(Request $request, $uid, $timestamp, $hash) {
       // A different user is already logged in on the computer.
       else {
         /** @var \Drupal\user\UserInterface $reset_link_user */
-        if ($reset_link_user = $this->userStorage->load($uid)) {
+        $reset_link_user = $this->userStorage->load($uid);
+        if ($reset_link_user && $this->validatePathParameters($reset_link_user, $timestamp, $hash)) {
           $this->messenger()
             ->addWarning($this->t('Another user (%other_user) is already logged into the site on this computer, but you tried to use a one-time link for user %resetting_user. <a href=":logout">Log out</a> and try using the link again.',
               [
@@ -302,7 +303,7 @@ protected function determineErrorRedirect(?UserInterface $user, int $timestamp,
       $this->messenger()->addError($this->t('You have tried to use a one-time login link that has expired. Request a new one using the form below.'));
       return $this->redirect('user.pass');
     }
-    elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && hash_equals($hash, user_pass_rehash($user, $timestamp))) {
+    elseif ($user->isAuthenticated() && $this->validatePathParameters($user, $timestamp, $hash, $timeout)) {
       // The information provided is valid.
       return NULL;
     }
@@ -311,6 +312,27 @@ protected function determineErrorRedirect(?UserInterface $user, int $timestamp,
     return $this->redirect('user.pass');
   }
 
+  /**
+   * Validates hash and timestamp.
+   *
+   * @param \Drupal\user\UserInterface $user
+   *   User requesting reset.
+   * @param int $timestamp
+   *   The timestamp.
+   * @param string $hash
+   *   Login link hash.
+   * @param int $timeout
+   *   Link expiration timeout.
+   *
+   * @return bool
+   *   Whether the provided data are valid.
+   */
+  protected function validatePathParameters(UserInterface $user, int $timestamp, string $hash, int $timeout = 0): bool {
+    $current = \Drupal::time()->getRequestTime();
+    $timeout_valid = ((!empty($timeout) && $current - $timestamp < $timeout) || empty($timeout));
+    return ($timestamp >= $user->getLastLoginTime()) && $timestamp <= $current && $timeout_valid && hash_equals($hash, user_pass_rehash($user, $timestamp));
+  }
+
   /**
    * Redirects users to their profile page.
    *
@@ -382,13 +404,12 @@ public function logout() {
   public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass = '') {
     // Time out in seconds until cancel URL expires; 24 hours = 86400 seconds.
     $timeout = 86400;
-    $current = REQUEST_TIME;
 
     // Basic validation of arguments.
     $account_data = $this->userData->get('user', $user->id());
     if (isset($account_data['cancel_method']) && !empty($timestamp) && !empty($hashed_pass)) {
       // Validate expiration and hashed password/login.
-      if ($timestamp <= $current && $current - $timestamp < $timeout && $user->id() && $timestamp >= $user->getLastLoginTime() && hash_equals($hashed_pass, user_pass_rehash($user, $timestamp))) {
+      if ($user->id() && $this->validatePathParameters($user, $timestamp, $hashed_pass, $timeout)) {
         $edit = [
           'user_cancel_notify' => $account_data['cancel_notify'] ?? $this->config('user.settings')->get('notify.status_canceled'),
         ];
diff --git a/core/modules/user/src/Form/EntityPermissionsForm.php b/core/modules/user/src/Form/EntityPermissionsForm.php
index d1a49cf4568fe5124d21634221b1227d2ce48910..126eb7b975b8067a1e63ab1802d3f77e32e958f9 100644
--- a/core/modules/user/src/Form/EntityPermissionsForm.php
+++ b/core/modules/user/src/Form/EntityPermissionsForm.php
@@ -153,6 +153,10 @@ public function buildForm(array $form, FormStateInterface $form_state, string $b
    *   The access result.
    */
   public function access(Route $route, RouteMatchInterface $route_match, $bundle = NULL): AccessResultInterface {
+    $permission = $route->getRequirement('_permission');
+    if ($permission && !$this->currentUser()->hasPermission($permission)) {
+      return AccessResult::neutral()->cachePerPermissions();
+    }
     // Set $this->bundle for use by ::permissionsByProvider().
     if ($bundle instanceof EntityInterface) {
       $this->bundle = $bundle;
diff --git a/core/modules/user/src/Plugin/LanguageNegotiation/LanguageNegotiationUserAdmin.php b/core/modules/user/src/Plugin/LanguageNegotiation/LanguageNegotiationUserAdmin.php
index fec14c3262b13b3c77212dc2dfca8ca374a699b8..7eba82eec126e7b389936be8d5a8da783994d50c 100644
--- a/core/modules/user/src/Plugin/LanguageNegotiation/LanguageNegotiationUserAdmin.php
+++ b/core/modules/user/src/Plugin/LanguageNegotiation/LanguageNegotiationUserAdmin.php
@@ -10,8 +10,8 @@
 use Drupal\Core\Routing\RouteObjectInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
-use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\Routing\Exception\ExceptionInterface;
 use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
 
 /**
@@ -137,10 +137,7 @@ protected function isAdminPath(Request $request) {
           $path = $this->pathProcessorManager->processInbound(urldecode(rtrim($cloned_request->getPathInfo(), '/')), $cloned_request);
           $attributes = $this->router->match($path);
         }
-        catch (ResourceNotFoundException $e) {
-          return FALSE;
-        }
-        catch (AccessDeniedHttpException $e) {
+        catch (ExceptionInterface | HttpException) {
           return FALSE;
         }
         $route_object = $attributes[RouteObjectInterface::ROUTE_OBJECT];
diff --git a/core/modules/user/src/ToolbarLinkBuilder.php b/core/modules/user/src/ToolbarLinkBuilder.php
index b278db6dba4c253220e58b28194d921436cf96e3..8d989b339e5232f7c8fc2bd3975509671f19191c 100644
--- a/core/modules/user/src/ToolbarLinkBuilder.php
+++ b/core/modules/user/src/ToolbarLinkBuilder.php
@@ -80,7 +80,7 @@ public function renderToolbarLinks() {
    */
   public function renderDisplayName() {
     return [
-      '#markup' => $this->account->getDisplayName(),
+      '#plain_text' => $this->account->getDisplayName(),
     ];
   }
 
diff --git a/core/modules/user/tests/modules/user_language_test/src/Controller/UserLanguageTestController.php b/core/modules/user/tests/modules/user_language_test/src/Controller/UserLanguageTestController.php
new file mode 100644
index 0000000000000000000000000000000000000000..280dafdaad8199b0e9dd72a2574f7a1222a1d04e
--- /dev/null
+++ b/core/modules/user/tests/modules/user_language_test/src/Controller/UserLanguageTestController.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Drupal\user_language_test\Controller;
+
+/**
+ * Returns responses for User Language Test routes.
+ */
+class UserLanguageTestController {
+
+  /**
+   * Builds the response.
+   */
+  public function buildPostResponse() {
+    return ['#markup' => 'It works!'];
+  }
+
+}
diff --git a/core/modules/user/tests/modules/user_language_test/src/Form/UserLanguageTestForm.php b/core/modules/user/tests/modules/user_language_test/src/Form/UserLanguageTestForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..f20d044840c2b4ce85e565f233a8a0ef080768ce
--- /dev/null
+++ b/core/modules/user/tests/modules/user_language_test/src/Form/UserLanguageTestForm.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\user_language_test\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Provides a User Language Test form.
+ */
+class UserLanguageTestForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'user_language_test';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+
+    $form['#action'] = Url::fromRoute('user_language_test.post_response')->toString();
+
+    $form['actions'] = [
+      '#type' => 'actions',
+      'submit' => [
+        '#type' => 'submit',
+        '#value' => $this->t('Send'),
+      ],
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+
+  }
+
+}
diff --git a/core/modules/user/tests/modules/user_language_test/user_language_test.info.yml b/core/modules/user/tests/modules/user_language_test/user_language_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5df005c016de9de76075c89a096b7d553156cd73
--- /dev/null
+++ b/core/modules/user/tests/modules/user_language_test/user_language_test.info.yml
@@ -0,0 +1,5 @@
+name: 'User language tests'
+type: module
+description: 'Support module for user language testing.'
+package: Testing
+version: VERSION
diff --git a/core/modules/user/tests/modules/user_language_test/user_language_test.routing.yml b/core/modules/user/tests/modules/user_language_test/user_language_test.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c8ddc89d21f79c0c37d4f870ab96428b69c74caf
--- /dev/null
+++ b/core/modules/user/tests/modules/user_language_test/user_language_test.routing.yml
@@ -0,0 +1,16 @@
+user_language_test.post_response:
+  path: '/user-language-test/post'
+  defaults:
+    _controller: Drupal\user_language_test\Controller\UserLanguageTestController::buildPostResponse
+  methods: [post]
+  options:
+    _admin_route: TRUE
+  requirements:
+    _access: 'TRUE'
+
+user_language_test.form:
+  path: '/user-language-test/form'
+  defaults:
+    _form: 'Drupal\user_language_test\Form\UserLanguageTestForm'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_access_perm.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_access_perm.yml
index b81c81d1fefc121c910580eeacd8249ec27f1096..a4c73fdbb8ee5ebb823566b8e981690b24794cb0 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_access_perm.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_access_perm.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_access_perm
-label: ''
+label: test_access_perm
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_access_role.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_access_role.yml
index 75808a070c6dee4362129cc6c81492de6ec6ae50..96018d032d158572315bfc357deda9a6d904b808 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_access_role.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_access_role.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_access_role
-label: ''
+label: test_access_role
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_field_permission.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_field_permission.yml
index f62a841e49e5d499fd3804acc8ec70aea759d684..db98731c766ff8a503166bafbe22c5b6f96a3532 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_field_permission.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_field_permission.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_field_permission
-label: null
+label: test_node_revision_vid
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_filter_permission.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_filter_permission.yml
index 6e23149aac5ff3a984ac95e2c79febbd13d54fe7..48f9755f32b50d78eac8400f26bd72f5241ff1db 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_filter_permission.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_filter_permission.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_filter_permission
-label: null
+label: test_filter_permission
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_plugin_argument_default_current_user.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_plugin_argument_default_current_user.yml
index aa0aea65d9172cb8e42c1290e859f86c2dff31bf..3e726a48a417fe800a94fe4390956d25a6ffdd23 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_plugin_argument_default_current_user.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_plugin_argument_default_current_user.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_plugin_argument_default_current_user
-label: ''
+label: test_plugin_argument_default_current_user
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml
index 2e59c171181200597f86824d014e5f7ea7298b54..7858370e137d3e0ee838ab5c31a07223897966a6 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_bulk_form.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_user_bulk_form
-label: ''
+label: test_user_bulk_form
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_changed.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_changed.yml
index fbe12fa42becf97dc5e85422242868d94eb63435..f3921915ef40c67bcf64c127b00b82c6563b1cd3 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_changed.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_changed.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_user_changed
-label: ''
+label: test_user_changed
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
index 2d379d3b256c43dbe313f1ffefc5c1d13156331d..ce16bacc3aae1797eae8b5035c6f2f2f17e09a51 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_fields_access.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_user_fields_access
-label: ''
+label: test_user_fields_access
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_name.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_name.yml
index 8371e2c0d145b51a754697f3113667e96d6322bb..9bcf36b3fb0870b09a17f31184f6709937374a97 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_name.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_name.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_user_name
-label: ''
+label: test_user_name
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_uid_argument.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_uid_argument.yml
index ab97e92784d301f5a91a94ac5ea7f53ebf060a99..cb25f74131f6669f3d83e15efd1bf8cd6a348055 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_uid_argument.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_uid_argument.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_user_uid_argument
-label: null
+label: test_user_uid_argument
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_view_argument_validate_user.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_view_argument_validate_user.yml
index 94607f74b7a1b2185725ee23947cd9148759c5d7..d89e4576f937a509aabbda53bb8c0757d5e632ab 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_view_argument_validate_user.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_view_argument_validate_user.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_argument_validate_user
-label: ''
+label: test_view_argument_validate_user
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_view_argument_validate_username.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_view_argument_validate_username.yml
index 478e6fb2f98589fb8ce9acc85f8e398992cdcb04..a0fd611ea3a4f34d7f1528638429db56570b15ab 100644
--- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_view_argument_validate_username.yml
+++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_view_argument_validate_username.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_argument_validate_username
-label: ''
+label: test_view_argument_validate_username
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/user/tests/src/Functional/GenericTest.php b/core/modules/user/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..46d76972c495370585ddd03549bfb7eed04d613c
--- /dev/null
+++ b/core/modules/user/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\user\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for user.
+ *
+ * @group user
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/user/tests/src/Functional/UserAdminLanguageTest.php b/core/modules/user/tests/src/Functional/UserAdminLanguageTest.php
index 2b29e802e58156949d44ecd45ee424c6d1c688c8..1a19144c982790c035f078fc8a33233a008306e3 100644
--- a/core/modules/user/tests/src/Functional/UserAdminLanguageTest.php
+++ b/core/modules/user/tests/src/Functional/UserAdminLanguageTest.php
@@ -31,7 +31,7 @@ class UserAdminLanguageTest extends BrowserTestBase {
    *
    * @var array
    */
-  protected static $modules = ['user', 'language', 'language_test'];
+  protected static $modules = ['user', 'language', 'language_test', 'user_language_test'];
 
   /**
    * {@inheritdoc}
@@ -156,6 +156,13 @@ public function testActualNegotiation() {
     $this->drupalGet('xx/' . $path);
     $this->assertSession()->pageTextContains('Language negotiation method: language-user-admin');
 
+    // Make sure 'language-user-admin' plugin does not fail when a route is
+    // restricted to POST requests and language negotiation with the admin
+    // language method is used.
+    $this->drupalGet('/user-language-test/form');
+    $this->submitForm([], 'Send');
+    $this->assertSession()->statusCodeEquals(200);
+
     // Unset the preferred language code for the user.
     $edit = [];
     $edit['preferred_admin_langcode'] = '';
diff --git a/core/modules/user/tests/src/Functional/UserAdminTest.php b/core/modules/user/tests/src/Functional/UserAdminTest.php
index 046a4095c1b4c50a194a41aabc7df6b663141ab6..8c79b8fa620bb45bd1f021a1e8f527e548d8b29b 100644
--- a/core/modules/user/tests/src/Functional/UserAdminTest.php
+++ b/core/modules/user/tests/src/Functional/UserAdminTest.php
@@ -143,15 +143,15 @@ public function testUserAdmin() {
     $this->assertSession()->elementExists('xpath', static::getLinkSelectorForUser($user_c));
 
     // Test unblocking of a user from /admin/people page and sending of activation mail
-    $editunblock = [];
-    $editunblock['action'] = 'user_unblock_user_action';
-    $editunblock['user_bulk_form[4]'] = TRUE;
+    $edit_unblock = [];
+    $edit_unblock['action'] = 'user_unblock_user_action';
+    $edit_unblock['user_bulk_form[4]'] = TRUE;
     $this->drupalGet('admin/people', [
       // Sort the table by username so that we know reliably which user will be
       // targeted with the blocking action.
       'query' => ['order' => 'name', 'sort' => 'asc'],
     ]);
-    $this->submitForm($editunblock, 'Apply to selected items');
+    $this->submitForm($edit_unblock, 'Apply to selected items');
     $user_storage->resetCache([$user_c->id()]);
     $account = $user_storage->load($user_c->id());
     $this->assertTrue($account->isActive(), 'User C unblocked');
diff --git a/core/modules/user/tests/src/Functional/UserPasswordResetTest.php b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
index ca40e9a6621075ea98fd5bfc795bebbce9b2ff46..ab202ee7ea8d8d79c57e5c3e7bd46b2c326e6038 100644
--- a/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
+++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
@@ -339,6 +339,13 @@ public function testUserPasswordResetLoggedIn() {
     $this->assertSession()->linkExists('Log out');
     $this->assertSession()->linkByHrefExists(Url::fromRoute('user.logout')->toString());
 
+    // Verify that the invalid password reset page does not show the user name.
+    $attack_reset_url = "user/reset/" . $another_account->id() . "/1/1";
+    $this->drupalGet($attack_reset_url);
+    $this->assertSession()->pageTextNotContains($another_account->getAccountName());
+    $this->assertSession()->addressEquals('user/' . $this->account->id());
+    $this->assertSession()->pageTextContains('The one-time login link you clicked is invalid.');
+
     $another_account->delete();
     $this->drupalGet($resetURL);
     $this->assertSession()->pageTextContains('The one-time login link you clicked is invalid.');
diff --git a/core/modules/user/tests/src/Functional/Views/HandlerFieldRoleTest.php b/core/modules/user/tests/src/Functional/Views/HandlerFieldRoleTest.php
index 3f68c071a63e0c9e17f619d47e70a06040f9a561..465420f8ea862631ef9481cff7846e69ed902e5f 100644
--- a/core/modules/user/tests/src/Functional/Views/HandlerFieldRoleTest.php
+++ b/core/modules/user/tests/src/Functional/Views/HandlerFieldRoleTest.php
@@ -27,29 +27,29 @@ class HandlerFieldRoleTest extends UserTestBase {
 
   public function testRole() {
     // Create a couple of roles for the view.
-    $rolename_a = 'a' . $this->randomMachineName(8);
-    $this->drupalCreateRole(['access content'], $rolename_a, '<em>' . $rolename_a . '</em>', 9);
+    $role_name_a = 'a' . $this->randomMachineName(8);
+    $this->drupalCreateRole(['access content'], $role_name_a, '<em>' . $role_name_a . '</em>', 9);
 
-    $rolename_b = 'b' . $this->randomMachineName(8);
-    $this->drupalCreateRole(['access content'], $rolename_b, $rolename_b, 8);
+    $role_name_b = 'b' . $this->randomMachineName(8);
+    $this->drupalCreateRole(['access content'], $role_name_b, $role_name_b, 8);
 
-    $rolename_not_assigned = $this->randomMachineName(8);
-    $this->drupalCreateRole(['access content'], $rolename_not_assigned, $rolename_not_assigned);
+    $role_name_not_assigned = $this->randomMachineName(8);
+    $this->drupalCreateRole(['access content'], $role_name_not_assigned, $role_name_not_assigned);
 
     // Add roles to user 1.
     $user = User::load(1);
-    $user->addRole($rolename_a);
-    $user->addRole($rolename_b);
+    $user->addRole($role_name_a);
+    $user->addRole($role_name_b);
     $user->save();
 
     $this->drupalLogin($this->createUser(['access user profiles']));
     $this->drupalGet('/test-views-handler-field-role');
     // Verify that the view test_views_handler_field_role renders role assigned
     // to user in the correct order and markup in role names is escaped.
-    $this->assertSession()->responseContains($rolename_b . Html::escape('<em>' . $rolename_a . '</em>'));
+    $this->assertSession()->responseContains($role_name_b . Html::escape('<em>' . $role_name_a . '</em>'));
     // Verify that the view test_views_handler_field_role does not render a role
     // not assigned to a user.
-    $this->assertSession()->pageTextNotContains($rolename_not_assigned);
+    $this->assertSession()->pageTextNotContains($role_name_not_assigned);
   }
 
 }
diff --git a/core/modules/user/tests/src/Unit/ToolbarLinkBuilderTest.php b/core/modules/user/tests/src/Unit/ToolbarLinkBuilderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..40be72a666452f733e36613c7b9c60e06fde3e62
--- /dev/null
+++ b/core/modules/user/tests/src/Unit/ToolbarLinkBuilderTest.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\Tests\user\Unit;
+
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Tests\UnitTestCase;
+use Drupal\user\ToolbarLinkBuilder;
+
+/**
+ * Tests user's ToolbarLinkBuilder.
+ *
+ * @coversDefaultClass \Drupal\user\ToolbarLinkBuilder
+ * @group user
+ */
+class ToolbarLinkBuilderTest extends UnitTestCase {
+
+  /**
+   * Tests structure of display name render array.
+   *
+   * @covers ::renderDisplayName
+   */
+  public function testRenderDisplayName() {
+    $account = $this->prophesize(AccountProxyInterface::class);
+    $display_name = 'Something suspicious that should be #plain_text, not #markup';
+    $account->getDisplayName()->willReturn($display_name);
+    $toolbar_link_builder = new ToolbarLinkBuilder($account->reveal());
+    $expected = ['#plain_text' => $display_name];
+    $this->assertSame($expected, $toolbar_link_builder->renderDisplayName());
+  }
+
+}
diff --git a/core/modules/views/config/schema/views.schema.yml b/core/modules/views/config/schema/views.schema.yml
index 01a9af681e0496d76172e0870ee4ad20383b2dc8..c279ee0d23f4622479a4eabe2b65c85f6d59fec8 100644
--- a/core/modules/views/config/schema/views.schema.yml
+++ b/core/modules/views/config/schema/views.schema.yml
@@ -75,7 +75,7 @@ views.view.*:
           # @see \Drupal\views_ui\ViewAddForm::form()
           max: 128
     label:
-      type: label
+      type: required_label
       label: 'Label'
     module:
       type: string
diff --git a/core/modules/views/js/base.js b/core/modules/views/js/base.js
index 378d43fa216d3c56654f1c2c39b6e3707e809d4b..050a3b67427d0b9d8ede2861e8761d0a66502dd2 100644
--- a/core/modules/views/js/base.js
+++ b/core/modules/views/js/base.js
@@ -29,9 +29,13 @@
     for (let i = 0; i < pairs.length; i++) {
       pair = pairs[i].split('=');
       // Ignore the 'q' path argument, if present.
-      if (pair[0] !== 'q' && pair[1]) {
-        args[decodeURIComponent(pair[0].replace(/\+/g, ' '))] =
-          decodeURIComponent(pair[1].replace(/\+/g, ' '));
+      if (pair[0] !== 'q') {
+        if (pair[1]) {
+          args[decodeURIComponent(pair[0].replace(/\+/g, ' '))] =
+            decodeURIComponent(pair[1].replace(/\+/g, ' '));
+        } else {
+          args[decodeURIComponent(pair[0].replace(/\+/g, ' '))] = '';
+        }
       }
     }
     return args;
diff --git a/core/modules/views/src/Entity/View.php b/core/modules/views/src/Entity/View.php
index 9bf93e06890622f6ec12d1d7e775f4035eca3455..8fb42eadf47eb18497b612d21fedeb92b727117f 100644
--- a/core/modules/views/src/Entity/View.php
+++ b/core/modules/views/src/Entity/View.php
@@ -140,6 +140,7 @@ public function createDuplicate() {
    */
   public function label() {
     if (!$label = $this->get('label')) {
+      @trigger_error('Saving a view without an explicit label is deprecated in drupal:10.2.0 and will raise an error in drupal:11.0.0. See https://www.drupal.org/node/3381669', E_USER_DEPRECATED);
       $label = $this->id();
     }
     return $label;
diff --git a/core/modules/views/src/Plugin/views/display/Feed.php b/core/modules/views/src/Plugin/views/display/Feed.php
index f48d61361be9d2221c7a2188130be4b7e96e0f1a..16d40813005a7bb57b53f7a8250137188aac8599 100644
--- a/core/modules/views/src/Plugin/views/display/Feed.php
+++ b/core/modules/views/src/Plugin/views/display/Feed.php
@@ -325,7 +325,7 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) {
   /**
    * {@inheritdoc}
    */
-  public function attachTo(ViewExecutable $clone, $display_id, array &$build) {
+  public function attachTo(ViewExecutable $view, $display_id, array &$build) {
     $displays = $this->getOption('displays');
     if (empty($displays[$display_id])) {
       return;
@@ -333,19 +333,15 @@ public function attachTo(ViewExecutable $clone, $display_id, array &$build) {
 
     // Defer to the feed style; it may put in meta information, and/or
     // attach a feed icon.
-    $clone->setArguments($this->view->args);
-    $clone->setDisplay($this->display['id']);
-    $clone->buildTitle();
-    if ($plugin = $clone->display_handler->getPlugin('style')) {
-      $plugin->attachTo($build, $display_id, $clone->getUrl(), $clone->getTitle());
-      foreach ($clone->feedIcons as $feed_icon) {
+    $view->setArguments($this->view->args);
+    $view->setDisplay($this->display['id']);
+    $view->buildTitle();
+    if ($plugin = $view->display_handler->getPlugin('style')) {
+      $plugin->attachTo($build, $display_id, $view->getUrl(), $view->getTitle());
+      foreach ($view->feedIcons as $feed_icon) {
         $this->view->feedIcons[] = $feed_icon;
       }
     }
-
-    // Clean up.
-    $clone->destroy();
-    unset($clone);
   }
 
   /**
diff --git a/core/modules/views/src/Plugin/views/field/BulkForm.php b/core/modules/views/src/Plugin/views/field/BulkForm.php
index 32afb60995b317e0f3caa8a7f90386bfd9b31411..a2552bdca7d0b90f553fdf47db9a6bea317a89d4 100644
--- a/core/modules/views/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/views/src/Plugin/views/field/BulkForm.php
@@ -551,7 +551,13 @@ protected function loadEntityFromBulkFormKey($bulk_form_key) {
 
     // Load the entity or a specific revision depending on the given key.
     $storage = $this->entityTypeManager->getStorage($this->getEntityType());
-    $entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id);
+    if ($revision_id) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+      $entity = $storage->loadRevision($revision_id);
+    }
+    else {
+      $entity = $storage->load($id);
+    }
 
     if ($entity instanceof TranslatableInterface) {
       $entity = $entity->getTranslation($langcode);
diff --git a/core/modules/views/src/Plugin/views/field/FileSize.php b/core/modules/views/src/Plugin/views/field/FileSize.php
index 5af1b94a3c4016290a3242bdf1f0ce2976087c72..79fdcadb4dddb8591b512eb5a7f106c752151689 100644
--- a/core/modules/views/src/Plugin/views/field/FileSize.php
+++ b/core/modules/views/src/Plugin/views/field/FileSize.php
@@ -3,6 +3,7 @@
 namespace Drupal\views\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\views\ResultRow;
 
 /**
@@ -52,7 +53,7 @@ public function render(ResultRow $values) {
 
         case 'formatted':
         default:
-          return format_size($value);
+          return ByteSizeMarkup::create((int) $value);
       }
     }
     else {
diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
index f8d03e12b72c161945bf60633198685c5fc0a73b..1d24826b758124980db21829c33f3586e9f1e0e5 100644
--- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
+++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
@@ -46,6 +46,23 @@
  */
 abstract class FilterPluginBase extends HandlerBase implements CacheableDependencyInterface {
 
+  /**
+   * A list of restricted identifiers.
+   *
+   * This list contains strings that could cause clashes with other site
+   * operations when used as a filter identifier.
+   *
+   * @var array
+   */
+  const RESTRICTED_IDENTIFIERS = [
+    'value',
+    'q',
+    'destination',
+    '_format',
+    '_wrapper_format',
+    'token',
+  ];
+
   /**
    * The value.
    *
@@ -660,7 +677,8 @@ public function buildExposeForm(&$form, FormStateInterface $form_state) {
       '#default_value' => $this->options['expose']['identifier'],
       '#title' => $this->t('Filter identifier'),
       '#size' => 40,
-      '#description' => $this->t('This will appear in the URL after the ? to identify this filter. Cannot be blank. Only letters, digits and the dot ("."), hyphen ("-"), underscore ("_"), and tilde ("~") characters are allowed.'),
+      '#description' => $this->t('This will appear in the URL after the ? to identify this filter. Cannot be blank. Only letters, digits and the dot ("."), hyphen ("-"), underscore ("_"), and tilde ("~") characters are allowed. @reserved_identifiers are reserved words and cannot be used.',
+        ['@reserved_identifiers' => '"' . implode('", "', self::RESTRICTED_IDENTIFIERS) . '"']),
     ];
   }
 
@@ -771,7 +789,7 @@ protected function validateIdentifier($identifier, FormStateInterface $form_stat
     if (empty($identifier)) {
       $error = $this->t('The identifier is required if the filter is exposed.');
     }
-    elseif ($identifier == 'value') {
+    elseif (in_array($identifier, self::RESTRICTED_IDENTIFIERS)) {
       $error = $this->t('This identifier is not allowed.');
     }
     elseif (preg_match('/[^a-zA-Z0-9_~\.\-]+/', $identifier)) {
@@ -1029,7 +1047,8 @@ protected function buildExposedFiltersGroupForm(&$form, FormStateInterface $form
       '#default_value' => $identifier,
       '#title' => $this->t('Filter identifier'),
       '#size' => 40,
-      '#description' => $this->t('This will appear in the URL after the ? to identify this filter. Cannot be blank. Only letters, digits and the dot ("."), hyphen ("-"), underscore ("_"), and tilde ("~") characters are allowed.'),
+      '#description' => $this->t('This will appear in the URL after the ? to identify this filter. Cannot be blank. Only letters, digits and the dot ("."), hyphen ("-"), underscore ("_"), and tilde ("~") characters are allowed. @reserved_identifiers are reserved words and cannot be used.',
+        ['@reserved_identifiers' => '"' . implode('", "', self::RESTRICTED_IDENTIFIERS) . '"']),
     ];
     $form['group_info']['label'] = [
       '#type' => 'textfield',
@@ -1072,48 +1091,6 @@ protected function buildExposedFiltersGroupForm(&$form, FormStateInterface $form
       '#default_value' => $this->options['group_info']['remember'],
     ];
 
-    if (!empty($this->options['group_info']['identifier'])) {
-      $identifier = $this->options['group_info']['identifier'];
-    }
-    else {
-      $identifier = 'group_' . $this->options['expose']['identifier'];
-    }
-    $form['group_info']['identifier'] = [
-      '#type' => 'textfield',
-      '#default_value' => $identifier,
-      '#title' => $this->t('Filter identifier'),
-      '#size' => 40,
-      '#description' => $this->t('This will appear in the URL after the ? to identify this filter. Cannot be blank. Only letters, digits and the dot ("."), hyphen ("-"), underscore ("_"), and tilde ("~") characters are allowed.'),
-    ];
-    $form['group_info']['label'] = [
-      '#type' => 'textfield',
-      '#default_value' => $this->options['group_info']['label'],
-      '#title' => $this->t('Label'),
-      '#size' => 40,
-    ];
-    $form['group_info']['optional'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Optional'),
-      '#description' => $this->t('This exposed filter is optional and will have added options to allow it not to be set.'),
-      '#default_value' => $this->options['group_info']['optional'],
-    ];
-    $form['group_info']['widget'] = [
-      '#type' => 'radios',
-      '#default_value' => $this->options['group_info']['widget'],
-      '#title' => $this->t('Widget type'),
-      '#options' => [
-        'radios' => $this->t('Radios'),
-        'select' => $this->t('Select'),
-      ],
-      '#description' => $this->t('Select which kind of widget will be used to render the group of filters'),
-    ];
-    $form['group_info']['remember'] = [
-      '#type' => 'checkbox',
-      '#title' => $this->t('Remember'),
-      '#description' => $this->t('Remember the last setting the user gave this filter.'),
-      '#default_value' => $this->options['group_info']['remember'],
-    ];
-
     // The string '- Any -' will not be rendered.
     // @see theme_views_ui_build_group_filter_form()
     $groups = ['All' => $this->t('- Any -')];
diff --git a/core/modules/views/src/Plugin/views/filter/LatestRevision.php b/core/modules/views/src/Plugin/views/filter/LatestRevision.php
index 7930a7fb1cb753b5bae8dc753518a433bb20837a..095f6ddc78918bec5c4dd6fdfaa60c5ab8971a3e 100644
--- a/core/modules/views/src/Plugin/views/filter/LatestRevision.php
+++ b/core/modules/views/src/Plugin/views/filter/LatestRevision.php
@@ -93,21 +93,10 @@ public function query() {
     $entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
     $keys = $entity_type->getKeys();
 
-    $definition = [
-      'table' => $query_base_table,
-      'type' => 'LEFT',
-      'field' => $keys['id'],
-      'left_table' => $query_base_table,
-      'left_field' => $keys['id'],
-      'extra' => [
-        ['left_field' => $keys['revision'], 'field' => $keys['revision'], 'operator' => '>'],
-      ],
-    ];
-
-    $join = $this->joinHandler->createInstance('standard', $definition);
-
-    $join_table_alias = $query->addTable($query_base_table, $this->relationship, $join);
-    $query->addWhere($this->options['group'], "$join_table_alias.{$keys['id']}", NULL, 'IS NULL');
+    $subquery = $query->getConnection()->select($query_base_table, 'base_table');
+    $subquery->addExpression("MAX(base_table.{$keys['revision']})", $keys['revision']);
+    $subquery->groupBy("base_table.{$keys['id']}");
+    $query->addWhere($this->options['group'], "$query_base_table.{$keys['revision']}", $subquery, 'IN');
   }
 
 }
diff --git a/core/modules/views/src/Plugin/views/filter/LatestTranslationAffectedRevision.php b/core/modules/views/src/Plugin/views/filter/LatestTranslationAffectedRevision.php
index ecfcbe3749983aca4b21751f51c7402585849177..8bcd87b7de442af2d7271c016ac3cab3030bd8c3 100644
--- a/core/modules/views/src/Plugin/views/filter/LatestTranslationAffectedRevision.php
+++ b/core/modules/views/src/Plugin/views/filter/LatestTranslationAffectedRevision.php
@@ -93,23 +93,27 @@ public function query() {
     $entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
     $keys = $entity_type->getKeys();
 
+    $subquery = $query->getConnection()->select($query_base_table, 'base_table');
+    $subquery->addExpression("MAX(base_table.{$keys['revision']})", $keys['revision']);
+    $subquery->fields('base_table', [$keys['id'], 'langcode']);
+    $subquery->groupBy("base_table.{$keys['id']}");
+    $subquery->groupBy('base_table.langcode');
+    $subquery->condition('base_table.revision_translation_affected', '1');
+
     $definition = [
-      'table' => $query_base_table,
-      'type' => 'LEFT',
+      'table formula' => $subquery,
+      'type' => 'INNER',
       'field' => $keys['id'],
       'left_table' => $query_base_table,
       'left_field' => $keys['id'],
       'extra' => [
-        ['left_field' => $keys['revision'], 'field' => $keys['revision'], 'operator' => '>'],
+        ['left_field' => $keys['revision'], 'field' => $keys['revision'], 'operator' => '='],
         ['left_field' => 'langcode', 'field' => 'langcode', 'operator' => '='],
-        ['field' => 'revision_translation_affected', 'value' => '1', 'operator' => '='],
       ],
     ];
 
     $join = $this->joinHandler->createInstance('standard', $definition);
-
-    $join_table_alias = $query->addTable($query_base_table, $this->relationship, $join);
-    $query->addWhere($this->options['group'], "$join_table_alias.{$keys['id']}", NULL, 'IS NULL');
+    $query->addTable($query_base_table, $this->relationship, $join);
     $query->addWhere($this->options['group'], "$query_base_table.revision_translation_affected", '1', '=');
   }
 
diff --git a/core/modules/views/src/Plugin/views/query/Sql.php b/core/modules/views/src/Plugin/views/query/Sql.php
index 0247bf8c67d9554aacaffcf81b87d9cbdc17a444..e1ee41bf695f71f08d72b0b8c3b53d6ab125516a 100644
--- a/core/modules/views/src/Plugin/views/query/Sql.php
+++ b/core/modules/views/src/Plugin/views/query/Sql.php
@@ -1633,6 +1633,7 @@ public function loadEntities(&$results) {
 
     // Now load all revisions.
     foreach ($revision_ids_by_type as $entity_type => $revision_ids) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entity_storage */
       $entity_storage = $this->entityTypeManager->getStorage($entity_type);
       $entities = [];
 
diff --git a/core/modules/views/src/ViewsConfigUpdater.php b/core/modules/views/src/ViewsConfigUpdater.php
index a3af80c199f8ac0fbf39655dd7f1f2f3db2601dc..b3d0b1b637a6af81f25bdc076d58c2f69466f4fa 100644
--- a/core/modules/views/src/ViewsConfigUpdater.php
+++ b/core/modules/views/src/ViewsConfigUpdater.php
@@ -3,11 +3,13 @@
 namespace Drupal\views;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Field\Plugin\Field\FieldFormatter\TimestampFormatter;
+use Drupal\Core\Language\LanguageInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -148,10 +150,30 @@ public function updateAll(ViewEntityInterface $view) {
       if ($this->processDefaultArgumentSkipUrlUpdate($handler, $handler_type)) {
         $changed = TRUE;
       }
+      if ($this->addLabelIfMissing($view)) {
+        $changed = TRUE;
+      }
       return $changed;
     });
   }
 
+  /**
+   * Adds a label to views which don't have one.
+   *
+   * @param \Drupal\views\ViewEntityInterface $view
+   *   The view to update.
+   *
+   * @return bool
+   *   Whether the view was updated.
+   */
+  public function addLabelIfMissing(ViewEntityInterface $view): bool {
+    if (!$view->get('label')) {
+      $view->set('label', $view->id());
+      return TRUE;
+    }
+    return FALSE;
+  }
+
   /**
    * Add lazy load options to all responsive_image type field configurations.
    *
@@ -281,7 +303,7 @@ protected function processOembedEagerLoadFieldHandler(array &$handler, string $h
     $deprecations_triggered = &$this->triggeredDeprecations['3212351'][$view->id()];
     if ($this->deprecationsEnabled && $changed && !$deprecations_triggered) {
       $deprecations_triggered = TRUE;
-      @trigger_error(sprintf('The oEmbed loading attribute update for view "%s" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided configuration should be updated to accommodate the changes described at https://www.drupal.org/node/3275103.', $view->id()), E_USER_DEPRECATED);
+      @trigger_error(sprintf('The oEmbed loading attribute update for view "%s" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3275103', $view->id()), E_USER_DEPRECATED);
     }
 
     return $changed;
@@ -450,4 +472,58 @@ public function processDefaultArgumentSkipUrlUpdate(array &$handler, string $han
     return FALSE;
   }
 
+  /**
+   * Removes user context from all views using term filter configurations.
+   *
+   * @param \Drupal\views\ViewEntityInterface $view
+   *   The View to update.
+   *
+   * @return bool
+   *   Whether the view was updated.
+   */
+  public function needsTaxonomyTermFilterUpdate(ViewEntityInterface $view): bool {
+    return $this->processDisplayHandlers($view, TRUE, function (&$handler, $handler_type) use ($view) {
+      return $this->processTaxonomyTermFilterHandler($handler, $handler_type, $view);
+    });
+  }
+
+  /**
+   * Processes taxonomy_index_tid type filters.
+   *
+   * @param array $handler
+   *   A display handler.
+   * @param string $handler_type
+   *   The handler type.
+   * @param \Drupal\views\ViewEntityInterface $view
+   *   The View being updated.
+   *
+   * @return bool
+   *   Whether the handler was updated.
+   */
+  protected function processTaxonomyTermFilterHandler(array &$handler, string $handler_type, ViewEntityInterface $view): bool {
+    $changed = FALSE;
+
+    // Force view resave if using taxonomy id filter.
+    $plugin_id = $handler['plugin_id'] ?? '';
+    if ($handler_type === 'filter' && $plugin_id === 'taxonomy_index_tid') {
+
+      // This cannot be done in View::preSave() due to trusted data.
+      $executable = $view->getExecutable();
+      $displays = $view->get('display');
+      foreach ($displays as $display_id => &$display) {
+        $executable->setDisplay($display_id);
+
+        $cache_metadata = $executable->getDisplay()->calculateCacheMetadata();
+        $display['cache_metadata']['contexts'] = $cache_metadata->getCacheContexts();
+        // Always include at least the 'languages:' context as there will most
+        // probably be translatable strings in the view output.
+        $display['cache_metadata']['contexts'] = Cache::mergeContexts($display['cache_metadata']['contexts'], ['languages:' . LanguageInterface::TYPE_INTERFACE]);
+        sort($display['cache_metadata']['contexts']);
+      }
+      $view->set('display', $displays);
+      $changed = TRUE;
+    }
+    return $changed;
+  }
+
 }
diff --git a/core/modules/views/tests/modules/views_test_cacheable_metadata_calculation/config/install/views.view.test_cacheable_metadata_calculation.yml b/core/modules/views/tests/modules/views_test_cacheable_metadata_calculation/config/install/views.view.test_cacheable_metadata_calculation.yml
index fb7af90c3b5bdf27e81bc333d35db5d338ef3d25..f658f38c21d55d48b00fe70747ff3d3f3652713c 100644
--- a/core/modules/views/tests/modules/views_test_cacheable_metadata_calculation/config/install/views.view.test_cacheable_metadata_calculation.yml
+++ b/core/modules/views/tests/modules/views_test_cacheable_metadata_calculation/config/install/views.view.test_cacheable_metadata_calculation.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_cacheable_metadata_calculation
-label: ''
+label: test_cacheable_metadata_calculation
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml
index e5d4692071429bc82a6239ee7fc145f351454262..53fafe60698f3c3ef08645821716c38676695931 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_fields.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: entity_test_fields
-label: ''
+label: entity_test_fields
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml
index aa66b1525b07b28837e37840d20baba7067e94ae..d9f10168fbd93de66b8c70c3937aff4f0ea6d668 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.entity_test_row.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: entity_test_row
-label: ''
+label: entity_test_row
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_access_none.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_access_none.yml
index 9d40b00e3e561b2b674f17ec251c30789b852577..f24d3eb2a652d3c8350131be72143fffc73a955c 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_access_none.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_access_none.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_access_none
-label: ''
+label: test_access_none
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_aggregate_count.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_aggregate_count.yml
index 6f7924065c682b8cdbcce041dc354b704bcc6178..f23c327417656e3a5c35c6f1ec2903d92c598d15 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_aggregate_count.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_aggregate_count.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_aggregate_count
-label: ''
+label: test_aggregate_count
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_aggregate_count_function.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_aggregate_count_function.yml
index 1bd8840dfd6d301453d4568cb525f4b8440bb635..8c49db4315e0719fe2e664a010bf96e0a986ad47 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_aggregate_count_function.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_aggregate_count_function.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_aggregate_count_function
-label: ''
+label: test_aggregate_count_function
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_messages.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_messages.yml
index 52b5aac6385dbe2c1b6bc77720314dd9924a0d23..bdb96fbef183a9e5ea4d5be4382a438e326263da 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_messages.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_messages.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_area_messages
-label: ''
+label: test_area_messages
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_order.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_order.yml
index 6c898e74bad07f48b421d479d402e670e46c74a3..dfceb72476f5866eafece1b32334f742e41745df 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_order.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_order.yml
@@ -1,7 +1,7 @@
 langcode: en
 status: true
 id: test_area_order
-label: ''
+label: test_area_order
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_result.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_result.yml
index 05f574e59dd553f5df4bbab461f8324cc8b29d07..007b3afdd8a2354f7010af478493cd273f25419a 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_result.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_result.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_area_result
-label: ''
+label: test_area_result
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_title.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_title.yml
index 617085a3d831568a7add46eae99c43ae1958b7d7..12634d43c08b3f0b0832919ab94da68ee5f44f1b 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_title.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_title.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_area_title
-label: ''
+label: test_area_title
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_view.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_view.yml
index 64e7e6a2330d81b6041d216eddc39bf78afc8af8..24817623dc87c187cae5f83558ba03694d632b5d 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_view.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_area_view.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_area_view
-label: ''
+label: test_area_view
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument.yml
index 732681343dea395bfd476831c6f2517c7a2aa101..d52feff991876abed47be4285200eb71e8df561a 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument.yml
@@ -1,7 +1,7 @@
 langcode: en
 status: true
 dependencies: {  }
-label: ''
+label: test_argument
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_date.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_date.yml
index 7d497de5b323f7125b73e8d386c6fba9582310e6..fda842ad0d9141cbb75a4a70767a65de20f39770 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_date.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_date.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_argument_date
-label: ''
+label: test_argument_date
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_current_user.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_current_user.yml
index b9b907fc2cba8e6d7618cf71a177278fa232d69d..538c7e472ca6cabaf27a1de55460dc246e88ae75 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_current_user.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_current_user.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_argument_default_current_user
-label: ''
+label: test_argument_default_current_user
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_fixed.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_fixed.yml
index 4d65f2f2aba070c24a1c2bc205aa0bdd31f28927..ffaff5b7e0b47551f9aa4e77599e791cd787989a 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_fixed.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_fixed.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_argument_default_fixed
-label: ''
+label: test_argument_default_fixed
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_query_param.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_query_param.yml
index 9edc2c1febc93291f5cc0a39164eb306d1bd7191..dfe8ee2311032c07cd4add934e72c8a032abe054 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_query_param.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_default_query_param.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_argument_default_query_param
-label: ''
+label: test_argument_default_query_param
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_validation.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_validation.yml
index dc2243b642a3ba9e2841cdf6315575846d18db14..e7b5ebec86405bc286baef00cd88973a06c4751d 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_validation.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_argument_validation.yml
@@ -5,7 +5,7 @@ dependencies:
     - node
     - user
 id: test_argument_validation
-label: ''
+label: test_argument_validation
 module: views
 description: 'This view is used to test the argument validator schema.'
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_attachment_ui.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_attachment_ui.yml
index c4521b1ea949e937a2139e4b950dfe51bfda8fa7..ad6ebdc92f87b7075d31173a7c22bd26b7b86863 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_attachment_ui.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_attachment_ui.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - user
 id: test_attachment_ui
-label: null
+label: test_attachment_ui
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_block_exposed_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_block_exposed_ajax.yml
index 417ad8427023cbb94736568c77b756d98b11e232..649b5fe8abdef6223e4f45f8bf4745cfe8d0090e 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_block_exposed_ajax.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_block_exposed_ajax.yml
@@ -6,7 +6,7 @@ dependencies:
   module:
     - node
 id: test_block_exposed_ajax
-label: ''
+label: test_block_exposed_ajax
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_block_exposed_ajax_with_page.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_block_exposed_ajax_with_page.yml
index 5528c4c1fa0167f4cf035d2eca8fa3e8213bbe2a..c484c6b1389e7f4225c698245e928d4b7d191a15 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_block_exposed_ajax_with_page.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_block_exposed_ajax_with_page.yml
@@ -6,7 +6,7 @@ dependencies:
   module:
     - node
 id: test_block_exposed_ajax_with_page
-label: ''
+label: test_block_exposed_ajax_with_page
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml
index d3a04aafb18f198cdab512f10ad0c6071526b450..b029203289b46621b5c00381c755260a46f72d29 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_cache.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_cache
-label: ''
+label: test_cache
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml
index 78ec14de71302b13b183ad7458393523b92366b2..3ab5ecb6689400b5276c6c0e3ad40cf78bb0c62f 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_click_sort
+label: test_click_sort
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml
index bec04a1b3dc825a163d29a6c8a7b2a0f6a88fd94..bf65e0eaf3792bbbee1adb48e84b8b9eb682dcc8 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_click_sort_ajax.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_click_sort_ajax
+label: test_click_sort_ajax
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_destroy.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_destroy.yml
index 52fb64145303a4db78a0db9ba842e1e0a3b79f4a..2b5be0e166b384fd3d459c3e5138bef64c410d75 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_destroy.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_destroy.yml
@@ -6,7 +6,7 @@ dependencies:
     - node
     - user
 id: test_destroy
-label: ''
+label: test_destroy
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display.yml
index 9c01c98c48e515506310623d112686a3724b924d..61911a51e3018634d3cde72011f4c88183e9b8cb 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display.yml
@@ -5,7 +5,7 @@ dependencies:
     - node
     - user
 id: test_display
-label: ''
+label: test_display
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_defaults.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_defaults.yml
index dfb41a77885e5e0a80960a38c6258149515a11c9..17c2515dd8990e33deed108a66969acceaacc13c 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_defaults.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_defaults.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_display_defaults
-label: ''
+label: test_display_defaults
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_empty.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_empty.yml
index 4bd5c2311917de2c256feca3a8d657d40ec831b7..af1dc2e89463f59425717e6ca0188418679572b6 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_empty.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_empty.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_display_empty
-label: ''
+label: test_display_empty
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_entity_reference.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_entity_reference.yml
index bf544428bf01aeef70bef7ca52b508961fe17c40..b49cd2092aa00491af777f0c578e1c3a027b6231 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_entity_reference.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_entity_reference.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_display_entity_reference
+label: test_display_entity_reference
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_invalid.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_invalid.yml
index 358d8e8a5c6e7332a76c84ac2ee11a8e39ef97a6..0502189670e4bf4c6602c2155d35563569074f78 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_invalid.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_display_invalid.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_display_invalid
-label: ''
+label: test_display_invalid
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml
index 5412a6464a465d66d3e6104163348ddb8a29b465..d7edb8767f794070973e59777c7996c5ec964807 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml
@@ -4,7 +4,7 @@ dependencies:
   content:
     - entity_test:entity_test:aa0c61cb-b7bb-4795-972a-493dabcf529c
 id: test_entity_area
-label: ''
+label: test_entity_area
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_field_renderers.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_field_renderers.yml
index a7f915e648929a132ff687dc9be649438bcb590f..5ebc7f1ba43b71cfc56d4f5d7cc2efdcb8c099f5 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_field_renderers.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_field_renderers.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_entity_field_renderers
-label: ''
+label: test_entity_field_renderers
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml
index e431063fa85cc54afa3b5d1899681e7defab68a4..3fb24ce48a43e2eaf050c967748269d7b7d06500 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_multivalue_basefield.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - entity_test
 id: test_entity_multivalue_basefield
-label: ''
+label: test_entity_multivalue_basefield
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row.yml
index 58189a30cd3d3e3867195dd040b6beab04da3e89..0fcbab0d89f110fa852f3e0c9596f18a2f3ed58c 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_entity_row
-label: ''
+label: test_entity_row
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row_renderers.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row_renderers.yml
index 3947592d122be89dc4b3fe7f30e1925c290445d4..671b1eb78fbc7dcfd7336b95d5d79216bfb3af4f 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row_renderers.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_row_renderers.yml
@@ -6,7 +6,7 @@ dependencies:
   module:
     - node
 id: test_entity_row_renderers
-label: ''
+label: test_entity_row_renderers
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_type_filter.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_type_filter.yml
index d31420063455b42a4781c875a6b4607cc0e0308c..ee5354d3c3d9f195ef5f2a50d1f2d2a7686be903 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_type_filter.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_type_filter.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_entity_type_filter
-label: ''
+label: test_entity_type_filter
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_example_area.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_example_area.yml
index 7df3a2dade91f78742d4c7bc8eb050b73b051935..059f8b8157828699c4d897d0cad878d76b02594d 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_example_area.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_example_area.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_example_area
+label: test_example_area
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_example_area_access.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_example_area_access.yml
index ccec9150ee391a0c95f6b43c5dce3823991588f1..74b839ce4aaeba9c0f1e875a8a9472d22d5aca8e 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_example_area_access.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_example_area_access.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_example_area_access
+label: test_example_area_access
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_executable_displays.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_executable_displays.yml
index 91591a404f8e30406fd6ec0b7869516241dca879..62442345918308a92c5bc6b2f19b31cf50f78d41 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_executable_displays.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_executable_displays.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_executable_displays
-label: ''
+label: test_executable_displays
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_admin_ui.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_admin_ui.yml
index f5b34075d280f3dde2d0b020a999026e767cf150..1a073afaed6af004aac9388062c5b2f9ffe2d678 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_admin_ui.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_admin_ui.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_exposed_admin_ui
-label: ''
+label: test_exposed_admin_ui
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_block.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_block.yml
index 44dbf6c64dc46d17d9d121e6f1fa03f7ec1bcfae..2df02b0b6633756e16b957d4fffc8cbefef3d1c1 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_block.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_block.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_exposed_block
-label: ''
+label: test_exposed_block
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_buttons.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_buttons.yml
index 651c632944e04226f727875b22c012f5d8a6c16c..9a3605845e80e46b34f856d3184f085471616c51 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_buttons.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_buttons.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_exposed_form_buttons
-label: ''
+label: test_exposed_form_buttons
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml
index 923c335cd1c17b98bf45ca6122574b8b79a6db84..a61c0adb625c1e519307119c4c4ff57605b7a87c 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_checkboxes.yml
@@ -7,7 +7,7 @@ dependencies:
     - node
     - taxonomy
 id: test_exposed_form_checkboxes
-label: ''
+label: test_exposed_form_checkboxes
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_pager.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_pager.yml
index d4324f180e093e01ec197ef08c5575f1b742cb6c..d091ef6762228b06692b037a68d4147b9c7a8251 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_pager.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_pager.yml
@@ -6,7 +6,7 @@ dependencies:
   module:
     - node
 id: test_exposed_form_pager
-label: ''
+label: test_exposed_form_pager
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_sort_items_per_page.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_sort_items_per_page.yml
index ad9a6412519c81c866a51e373f99d0aa70d1a0d2..940a6304495a68a66b902de483d654325bd3f718 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_sort_items_per_page.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_exposed_form_sort_items_per_page.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_exposed_form_sort_items_per_page
-label: ''
+label: test_exposed_form_sort_items_per_page
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_alias_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_alias_test.yml
index 95d128380279eecea19ab2c58650ed7b56bfda73..91f3cf49bad08f5c2c896b7c25eac3ece03196e0 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_alias_test.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_alias_test.yml
@@ -2,6 +2,7 @@ langcode: und
 status: true
 dependencies: {  }
 id: test_field_alias_test
+label: test_field_alias_test
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_argument_tokens.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_argument_tokens.yml
index 61c6cd3849f2509cfeb38b41febde7263ebe42de..48dc614fda65f152770ec04c7e87990323451d68 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_argument_tokens.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_argument_tokens.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_field_argument_tokens
-label: null
+label: test_field_argument_tokens
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_classes.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_classes.yml
index b573453ceafb234eca072a36ac670e759c298cda..17eb5ed9a271c415138d77b53bd5f630e19544ce 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_classes.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_classes.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_field_classes
+label: test_field_classes
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_config_translation_filter.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_config_translation_filter.yml
index 4b11689865be5168e4dff883d0439aea560c34a6..09e9a9e79d9bf3ee19d40cea377bc0e679cbbc44 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_config_translation_filter.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_config_translation_filter.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_field_config_translation_filter
+label: test_field_config_translation_filter
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_complex_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_complex_test.yml
index c7dd85315d4642217bcb145ca655776a5fbbbdef..61ba33c4742077682bc2d90663a6be3d29ef98bb 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_complex_test.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_complex_test.yml
@@ -2,6 +2,7 @@ langcode: und
 status: true
 dependencies: {  }
 id: test_field_field_complex_test
+label: test_field_field_complex_test
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml
index 4b80b0a48d4c61ae83073669035b9d1cdc9f38d1..2db8545dbfe52ec3cf583aef5f1582e187289e91 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_complex_test.yml
@@ -2,6 +2,7 @@ langcode: und
 status: true
 dependencies: {  }
 id: test_field_field_revision_complex_test
+label: test_field_field_revision_complex_test
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml
index 889b80efbe503dd1091794bdc510b9f7c0f62ad3..eed22a501113556039f2f6a56002b9c718bb34df 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml
@@ -2,6 +2,7 @@ langcode: und
 status: true
 dependencies: {  }
 id: test_field_field_revision_test
+label: test_field_field_revision_test
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_test.yml
index f1ffa60066c0d70ea118756cf6e5b9f5cbb26a60..0a5886885295a75676cc04c58bd97f83e1ca1559 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_test.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_test.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_field_field_test
+label: test_field_field_test
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml
index 75c4450b5e81299f850c4ba83befd602162d1998..091c4375635e54087dd79a478d8c102f549c0676 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_header.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_field_header
-label: null
+label: test_field_header
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_output.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_output.yml
index 37c448278e5ef94995cb8d7c0f7b4c3f2244e2c3..17a2127f2fbcd27d065e53e366c9c6cd2a5fc7cb 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_output.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_output.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_field_output
-label: ''
+label: test_field_output
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_tokens.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_tokens.yml
index 812a18f548916b5d951133c59e5719992e3cb2cf..2646d4c2091e04c4f3232daa1c301c4d7594f611 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_tokens.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_tokens.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_field_tokens
-label: null
+label: test_field_tokens
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_date_between.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_date_between.yml
index 622e7656ef24a125065637fc346e21caf7dc2d71..aa6d1a8086ea0321859c3cf3bb110310c51c9eed 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_date_between.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_date_between.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_filter_date_between
-label: ''
+label: test_filter_date_between
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_in_operator_ui.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_in_operator_ui.yml
index a19ded9a23754c6cab8bd3ca2e9d6be646f43b67..44177e0e186a9e3241fc5b8665fc5567a9eb8cde 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_in_operator_ui.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_filter_in_operator_ui.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_filter_in_operator_ui
-label: ''
+label: test_filter_in_operator_ui
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml
index cb3ffac90477ba1a3e9b1ca07a7ab5bce4e516e9..ddb305fb162e8873c6b3f4cda15a1aedd4260382 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_form_multiple.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_form_multiple
-label: ''
+label: test_form_multiple
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid.yml
index 5f22d825726a62ec2907dc48366637bb00bf3729..468e5e9ada78ad92091baa346b83ea6ceffd6fb9 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_grid
-label: ''
+label: test_grid
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid_responsive.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid_responsive.yml
index 3a1b395e55a3409829b97f345546898fbd9c68df..6464e40b822fcb3370f51fc5920b93d31d6349ad 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid_responsive.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_grid_responsive.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_grid_responsive
-label: ''
+label: test_grid_responsive
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count.yml
index 8ae7afc93f7b7fb00d953de0f6b032cfa29bda0e..68644e07b06a292344304895650acc3c34eee2e7 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_group_by_count
-label: ''
+label: test_group_by_count
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count_multicardinality.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count_multicardinality.yml
index cb7c4abd2a34169310031b3c61ad7c58592cbd8a..70fd62e380e6085994dfacb0456275e073cda52d 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count_multicardinality.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_count_multicardinality.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_group_by_count_multicardinality
-label: ''
+label: test_group_by_count_multicardinality
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_field_not_within_bundle.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_field_not_within_bundle.yml
index 34cc961491fe766459c99d764af19553a62ae25d..32b3ca7acfd0abb62a90141cb3ae6dc58e55dadb 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_field_not_within_bundle.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_field_not_within_bundle.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_group_by_field_not_within_bundle
-label: ''
+label: test_group_by_field_not_within_bundle
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_in_filters.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_in_filters.yml
index 7e3d6af0a0aa20541a04ca315c0cbb6eb0de31fd..0086099c72767e256c8fb1736b1a5d4b70ec9a6b 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_in_filters.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_group_by_in_filters.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_group_by_in_filters
-label: ''
+label: test_group_by_in_filters
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_relationships.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_relationships.yml
index 62caad2d5254f44129932acb47117227ffcbbe4e..38fc840d9c5c5f2dbfc2e024ffeb8b7a674ee649 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_relationships.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_relationships.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_handler_relationships
-label: ''
+label: test_handler_relationships
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_test_access.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_test_access.yml
index cd4c50b561b9fb4208cdc477361b9a2a80d65170..ec2a1c60caecbeb6d93f53ddb4b4fac8e0abb4f4 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_test_access.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_handler_test_access.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_handler_test_access
-label: null
+label: test_handler_test_access
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_latest_revision_filter.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_latest_revision_filter.yml
index 2e8477c792531dedab8f1df04b82b929c03bee32..b7e97d15bb30abcf028a9129866b7abf560b27c3 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_latest_revision_filter.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_latest_revision_filter.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_latest_revision_filter
-label: ''
+label: test_latest_revision_filter
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_menu_link.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_menu_link.yml
index f0ca7fdf5f643877f2bdcb32876db38ec8ff3eed..d8d2144d21cba3b164972376316ed0a7cb8aa5de 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_menu_link.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_menu_link.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_menu_link
-label: ''
+label: test_menu_link
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display.yml
index 66edf1fd8fab0d7d337ab47a769b71503a213300..858cb589400391cca4848456a2eac45cdbb3415b 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_page_display
-label: ''
+label: test_page_display
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_arguments.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_arguments.yml
index 41d95c291a05070dc4f0409898753efd61413d9d..b5d338f3d8fc22a97482e98a070f24af5d15e480 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_arguments.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_arguments.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_page_display_arguments
-label: ''
+label: test_page_display_arguments
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_path.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_path.yml
index 3ac774ea739df28d6ff60bd920c81d0393354423..bb89372110771d973c4631d2bbce4d586645d0ea 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_path.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_path.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_page_display_path
-label: ''
+label: test_page_display_path
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_route.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_route.yml
index 4e2ab7007dbe4ed261d753563ae51e0036480d58..f110099da308fadb8a7e4b9988555ed34d2e06f2 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_route.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_display_route.yml
@@ -6,7 +6,7 @@ dependencies:
   content:
     - StaticTest
 id: test_page_display_route
-label: ''
+label: test_page_display_route
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_view.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_view.yml
index 7bbd0b51a52677c420e960708e5d112407de5e63..9b3ecc8d1336f17c339beec43edaa5bed5ee2ced 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_view.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_page_view.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_page_view
-label: ''
+label: test_page_view
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_full.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_full.yml
index 968f0e03b1b8bd9e68f12194f85d93e0df3a54bb..0a7944b51fcfae83a51fca48e39eeb9872289d4e 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_full.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_full.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_pager_full
-label: ''
+label: test_pager_full
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_full_ajax.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_full_ajax.yml
index ef6ff80ee3e38ec0d8cb31d9cbe73f3c489af944..bf9ac37cb5e25d7fd0a87648052c39aac02a217d 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_full_ajax.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_full_ajax.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_pager_full_ajax
-label: ''
+label: test_pager_full_ajax
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_none.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_none.yml
index dea4015cd972ab8744d309bb72ca247a8b3137df..ac76ba67eead69540bdd6823f80aae2376d687cc 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_none.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_none.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_pager_none
-label: ''
+label: test_pager_none
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_some.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_some.yml
index b5b2266d372d581bb9e66fdb29db991e240bb582..c5a8a03dc84327e8692cd91dc99781f584a4e983 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_some.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_pager_some.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_pager_some
-label: ''
+label: test_pager_some
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_preprocess.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_preprocess.yml
index 1744725a3c5256d51fe7f1ffb7b86020f4ace437..1023a2381ae425f7570a3590c9faaf3cde72ed2f 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_preprocess.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_preprocess.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_preprocess
-label: ''
+label: test_preprocess
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_remember_selected.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_remember_selected.yml
index 8ae1034ab0da67f3420a298dc004498d5e3556bb..11b3ceec29c21f471749820eca83e26f3f9e735c 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_remember_selected.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_remember_selected.yml
@@ -6,7 +6,7 @@ dependencies:
   module:
     - node
 id: test_remember_selected
-label: ''
+label: test_remember_selected
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_simple_argument.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_simple_argument.yml
index 13ce51cc6e362e64cc68f93978abf150d1440447..0be9c8f26a4173db69109b080c7e0011e14644bf 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_simple_argument.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_simple_argument.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_simple_argument
-label: ''
+label: test_simple_argument
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_store_pager_settings.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_store_pager_settings.yml
index 033fa0ffca0a191206d779b2f288fca8ff6d45a5..cc2d637cfdba45580a3260fa8a5ba19ead877c8f 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_store_pager_settings.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_store_pager_settings.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_store_pager_settings
-label: ''
+label: test_store_pager_settings
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_style_html_list.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_style_html_list.yml
index 5b2a751df77c6477e87b14a763338dbcd6865caf..60873440beb2bf49e39281fe997f9d5eacd2c21f 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_style_html_list.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_style_html_list.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_style_html_list
-label: ''
+label: test_style_html_list
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_style_mapping.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_style_mapping.yml
index 75abedb96998b75544d1740404bd51fb29df3b01..b7189334e0afea52d31154f09c57a4ec2f4ca8ab 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_style_mapping.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_style_mapping.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - views_test_data
 id: test_style_mapping
-label: ''
+label: test_style_mapping
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml
index a103bfb783d305202a66d178f9862f112805fc09..72e5df81756ae362145999ae44d26ce08c11cf3d 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_table.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_table
-label: ''
+label: test_table
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_tag_cache.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_tag_cache.yml
index 57bdbe35316b78ff847b14ecfe05fb3ed059459a..4cd7cc42569454d76cf673a056711d5fb274f08f 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_tag_cache.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_tag_cache.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_tag_cache
-label: ''
+label: test_tag_cache
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_argument_validate_numeric.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_argument_validate_numeric.yml
index 65189907c280ac1ed1487ffe82d0432ce6ec1a5a..d051c4bde22606644eba08232a8f7fb270cd300f 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_argument_validate_numeric.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_argument_validate_numeric.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_argument_validate_numeric
-label: ''
+label: test_view_argument_validate_numeric
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_empty.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_empty.yml
index 98d67ca24ad6030dfe9cf2052e146a40b405f15e..10e5447ad44402773bb20224e3afbe622aae38b7 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_empty.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_empty.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_empty
-label: ''
+label: test_view_empty
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test.yml
index 265dbff23565f11358987c82777ccc83537fa7da..79ff9572c4668f86ffd81d2d7f50ab1b46359ece 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_entity_test
-label: ''
+label: test_view_entity_test
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_additional_base_field.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_additional_base_field.yml
index 3f9ead3986eb193b1a5749248904a3bc12036e36..f90efb0b6e05adb909ffe1714ec32d03e2ae9e02 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_additional_base_field.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_additional_base_field.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_entity_test_additional_base_field
-label: ''
+label: test_view_entity_test_additional_base_field
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_data.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_data.yml
index 38537706208841336b64b047824efcd612ec148d..e2390ebf214f70905f60febf0297f99e9f1171f0 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_data.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_data.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_entity_test_data
-label: ''
+label: test_view_entity_test_data
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_revision.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_revision.yml
index 40eb205b4ef10883d4114bfafdd26e89b1719f4c..3ec144b0f101f045d4dbd40f2a4b638440d32965 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_revision.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_entity_test_revision.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_entity_test_revision
-label: ''
+label: test_view_entity_test_revision
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_handler_weight.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_handler_weight.yml
index 11d07dfda65ab042ceb48f25595b44f56d806f8b..4cac6bb8c466aee35294eeec5c103b737872a6a1 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_handler_weight.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_handler_weight.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_handler_weight
-label: ''
+label: test_view_handler_weight
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_pager_full_zero_items_per_page.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_pager_full_zero_items_per_page.yml
index a3a5a6f122ae53e571ff597d96c0f9f31a146430..7153b140ea14bf5f7ce01070c209b2fb9b3ccf56 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_pager_full_zero_items_per_page.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_pager_full_zero_items_per_page.yml
@@ -4,7 +4,7 @@ dependencies:
   module:
     - node
 id: test_view_pager_full_zero_items_per_page
-label: ''
+label: test_view_pager_full_zero_items_per_page
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml
index b540ccb5a5fa6c9610002ea5d471b8bc52dfcfae..cec43f7c7dc7ea1c975fbca1c29d80437aae24b7 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_render.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_render
-label: ''
+label: test_view_render
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_sort_translation.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_sort_translation.yml
index 277dad29c63b7713fb3a120bfb7af4796e653efb..db73f59f866d7224925082106dcf509832302157 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_sort_translation.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_view_sort_translation.yml
@@ -2,6 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_view_sort_translation
+label: test_view_sort_translation
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_views_groupby_save.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_views_groupby_save.yml
index 1aad98355aaf321827d2f52830168e74223d1743..1e75c3f22610b917bc30526de7d005a44ab82d42 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_views_groupby_save.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_views_groupby_save.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies: {  }
 id: test_views_groupby_save
-label: ''
+label: test_views_groupby_save
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/modules/views_test_data/test_views/views.view.test_access_static.yml b/core/modules/views/tests/modules/views_test_data/test_views/views.view.test_access_static.yml
index ca4a26051a4ecb0c1579b56e98d9d4898ce57106..4853b050b59d56823fab89389472c6f199d2acde 100644
--- a/core/modules/views/tests/modules/views_test_data/test_views/views.view.test_access_static.yml
+++ b/core/modules/views/tests/modules/views_test_data/test_views/views.view.test_access_static.yml
@@ -6,7 +6,7 @@ dependencies:
   content:
     - StaticTest
 id: test_access_static
-label: ''
+label: test_access_static
 module: views
 description: ''
 tag: ''
diff --git a/core/modules/views/tests/src/Functional/GenericTest.php b/core/modules/views/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a2d8575e930e969a34a173f495a90ceab54e4f5
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\views\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for views.
+ *
+ * @group views
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/views/tests/src/Functional/Handler/HandlerTest.php b/core/modules/views/tests/src/Functional/Handler/HandlerTest.php
index 65810230ed48bd5981c97bbef1ad543a6ea0e908..b06a7f447592ba658024cd1dd7036eb7cc88f8ae 100644
--- a/core/modules/views/tests/src/Functional/Handler/HandlerTest.php
+++ b/core/modules/views/tests/src/Functional/Handler/HandlerTest.php
@@ -271,7 +271,11 @@ public function testRelationshipUI() {
     $this->assertSession()->fieldNotExists($relationship_name);
 
     // Create a view of comments with node relationship.
-    View::create(['base_table' => 'comment_field_data', 'id' => 'test_get_entity_type'])->save();
+    View::create([
+      'base_table' => 'comment_field_data',
+      'id' => 'test_get_entity_type',
+      'label' => 'Test',
+    ])->save();
     $this->drupalGet('admin/structure/views/nojs/add-handler/test_get_entity_type/default/relationship');
     $this->submitForm(['name[comment_field_data.node]' => 'comment_field_data.node'], 'Add and configure relationships');
     $this->submitForm([], 'Apply');
diff --git a/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php b/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php
index 4f6dd5b8446013c37ed51fc8ad3b59193b418987..dc5f4fa9ee57f9f16eb3cee9145a0c7fbc6f0b2c 100644
--- a/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php
+++ b/core/modules/views/tests/src/Functional/Plugin/ExposedFormTest.php
@@ -9,11 +9,13 @@
 use Drupal\views\ViewExecutable;
 use Drupal\views\Views;
 use Drupal\views\Entity\View;
+use Drupal\views\Plugin\views\filter\FilterPluginBase;
 
 /**
  * Tests exposed forms functionality.
  *
  * @group views
+ * @group #slow
  */
 class ExposedFormTest extends ViewTestBase {
 
@@ -161,6 +163,37 @@ public function testExposedIdentifier() {
       'page_1' => ['This identifier has illegal characters.'],
     ];
     $this->assertEquals($expected, $errors);
+
+    foreach (FilterPluginBase::RESTRICTED_IDENTIFIERS as $restricted_identifier) {
+      $view = Views::getView('test_exposed_form_buttons');
+      $view->setDisplay();
+      $view->displayHandlers->get('default')->overrideOption('filters', [
+        'type' => [
+          'exposed' => TRUE,
+          'field' => 'type',
+          'id' => 'type',
+          'table' => 'node_field_data',
+          'plugin_id' => 'in_operator',
+          'entity_type' => 'node',
+          'entity_field' => 'type',
+          'expose' => [
+            'identifier' => $restricted_identifier,
+            'label' => 'Content: Type',
+            'operator_id' => 'type_op',
+            'reduce' => FALSE,
+            'description' => 'Exposed overridden description',
+          ],
+        ],
+      ]);
+      $this->executeView($view);
+
+      $errors = $view->validate();
+      $expected = [
+        'default' => ['This identifier is not allowed.'],
+        'page_1' => ['This identifier is not allowed.'],
+      ];
+      $this->assertEquals($expected, $errors);
+    }
   }
 
   /**
diff --git a/core/modules/views/tests/src/Functional/Plugin/StyleTableTest.php b/core/modules/views/tests/src/Functional/Plugin/StyleTableTest.php
index 08755487ce12175ea18bafaeb458b515468946c2..c3e157ac41484eb56bff6e92a741e02ed8a9de41 100644
--- a/core/modules/views/tests/src/Functional/Plugin/StyleTableTest.php
+++ b/core/modules/views/tests/src/Functional/Plugin/StyleTableTest.php
@@ -224,6 +224,27 @@ public function testGrouping() {
     }
   }
 
+  /**
+   * Tests responsive classes and column assigning.
+   */
+  public function testResponsiveMergedColumns() {
+    /** @var \Drupal\views\ViewEntityInterface $view */
+    $view = \Drupal::entityTypeManager()->getStorage('view')->load('test_table');
+
+    // Merge the two job columns together and set the responsive priority on
+    // the column that is merged to.
+    $display = &$view->getDisplay('default');
+    $display['display_options']['style']['options']['columns']['job'] = 'job_1';
+    $display['display_options']['style']['options']['info']['job_1']['separator'] = ', ';
+    $display['display_options']['style']['options']['info']['job_1']['responsive'] = 'priority-low';
+    $view->save();
+
+    // Ensure that both columns are properly combined.
+    $this->drupalGet('test-table');
+    $this->assertSession()->elementExists('xpath', '//tbody/tr/td[contains(concat(" ", @class, " "), " priority-low views-field views-field-job views-field-job-1 ")]');
+    $this->assertSession()->elementExists('xpath', '//tbody/tr/td[contains(., "Drummer, Drummer")]');
+  }
+
   /**
    * Tests the cacheability of the table display.
    */
diff --git a/core/modules/views/tests/src/Functional/Update/ViewsAddMissingLabelsUpdateTest.php b/core/modules/views/tests/src/Functional/Update/ViewsAddMissingLabelsUpdateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa4f7de3f72c6508ef58dc229a7f129981448105
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/Update/ViewsAddMissingLabelsUpdateTest.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Drupal\Tests\views\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\views\Entity\View;
+
+/**
+ * Tests the upgrade path for adding missing labels.
+ *
+ * @see views_post_update_add_missing_labels()
+ *
+ * @group Update
+ * @group legacy
+ */
+class ViewsAddMissingLabelsUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['entity_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz',
+      __DIR__ . '/../../../fixtures/update/fix-revision-id-update.php',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->installModulesFromClassProperty($this->container);
+  }
+
+  /**
+   * Tests the upgrade path for adding missing labels.
+   */
+  public function testViewsPostUpdateFixRevisionId() {
+    $view = View::load('test_fix_revision_id_update');
+    $data = $view->toArray();
+    $this->assertEmpty($data['label']);
+
+    $this->runUpdates();
+
+    $view = View::load('test_fix_revision_id_update');
+    $data = $view->toArray();
+    $this->assertSame('test_fix_revision_id_update', $data['label']);
+  }
+
+}
diff --git a/core/modules/views/tests/src/FunctionalJavascript/PaginationAJAXTest.php b/core/modules/views/tests/src/FunctionalJavascript/PaginationAJAXTest.php
index 758a8a4676a983bbc1db19464cbca4c666b86c48..42aacebc1177dca0ed2402604b1613758bffe955 100644
--- a/core/modules/views/tests/src/FunctionalJavascript/PaginationAJAXTest.php
+++ b/core/modules/views/tests/src/FunctionalJavascript/PaginationAJAXTest.php
@@ -46,7 +46,11 @@ protected function setUp(): void {
     // Create a Content type and eleven test nodes.
     $this->createContentType(['type' => 'page']);
     for ($i = 1; $i <= 11; $i++) {
-      $this->createNode(['title' => 'Node ' . $i . ' content', 'changed' => $i * 1000]);
+      $fields = [
+        'title' => $i > 6 ? 'Node ' . $i . ' content' : 'Node ' . $i . ' content default_value',
+        'changed' => $i * 1000,
+      ];
+      $this->createNode($fields);
     }
 
     // Create a user privileged enough to view content.
@@ -90,18 +94,18 @@ public function testBasicPagination() {
     /** @var \Behat\Mink\Element\NodeElement[] $rows */
     $rows = $page->findAll('css', 'tbody tr');
     $this->assertCount(5, $rows);
-    $this->assertStringContainsString('Node 1 content', $rows[0]->getHtml());
+    $this->assertStringContainsString('Node 1 content default_value', $rows[0]->getHtml());
 
     $this->clickLink('Go to page 2');
     $session_assert->assertWaitOnAjaxRequest();
     $rows = $page->findAll('css', 'tbody tr');
     $this->assertCount(5, $rows);
-    $this->assertStringContainsString('Node 6 content', $rows[0]->getHtml());
+    $this->assertStringContainsString('Node 6 content default_value', $rows[0]->getHtml());
     $link = $page->findLink('Go to page 3');
     $this->assertNoDuplicateAssetsOnPage();
 
     // Test that no unwanted parameters are added to the URL.
-    $this->assertEquals('?status=All&type=All&langcode=All&items_per_page=5&order=changed&sort=asc&page=2', $link->getAttribute('href'));
+    $this->assertEquals('?status=All&type=All&title=&langcode=All&items_per_page=5&order=changed&sort=asc&page=2', $link->getAttribute('href'));
 
     $this->clickLink('Go to page 3');
     $session_assert->assertWaitOnAjaxRequest();
@@ -114,14 +118,14 @@ public function testBasicPagination() {
     $session_assert->assertWaitOnAjaxRequest();
     $rows = $page->findAll('css', 'tbody tr');
     $this->assertCount(5, $rows);
-    $this->assertStringContainsString('Node 1 content', $rows[0]->getHtml());
+    $this->assertStringContainsString('Node 1 content default_value', $rows[0]->getHtml());
 
     // Navigate using the 'next' link.
     $this->clickLink('Go to next page');
     $session_assert->assertWaitOnAjaxRequest();
     $rows = $page->findAll('css', 'tbody tr');
     $this->assertCount(5, $rows);
-    $this->assertStringContainsString('Node 6 content', $rows[0]->getHtml());
+    $this->assertStringContainsString('Node 6 content default_value', $rows[0]->getHtml());
 
     // Navigate using the 'last' link.
     $this->clickLink('Go to last page');
@@ -135,6 +139,104 @@ public function testBasicPagination() {
     $this->assertEquals($expected_view_path, current($settings['views']['ajaxViews'])['view_path']);
   }
 
+  /**
+   * Tests if pagination via AJAX works for the filter with default value.
+   */
+  public function testDefaultFilterPagination() {
+    // Add default value to the title filter.
+    $view = \Drupal::configFactory()->getEditable('views.view.test_content_ajax');
+    $display = $view->get('display');
+    $display['default']['display_options']['filters']['title']['value'] = 'default_value';
+    $view->set('display', $display);
+    $view->save();
+
+    // Visit the content page.
+    $this->drupalGet('test-content-ajax');
+
+    $session_assert = $this->assertSession();
+
+    $page = $this->getSession()->getPage();
+
+    $settings = $this->getDrupalSettings();
+
+    // Make sure that the view_path is set correctly.
+    $expected_view_path = '/test-content-ajax';
+    $this->assertEquals($expected_view_path, current($settings['views']['ajaxViews'])['view_path']);
+
+    // Set the number of items displayed per page to 5 using the exposed pager.
+    $page->selectFieldOption('edit-items-per-page', 5);
+    $page->pressButton('Filter');
+    $session_assert->assertWaitOnAjaxRequest();
+
+    // Change 'Updated' sorting from descending to ascending.
+    $page->clickLink('Updated');
+    $session_assert->assertWaitOnAjaxRequest();
+
+    // Use the pager by clicking on the links and test if we see the expected
+    // number of rows on each page. For easy targeting the titles of the pager
+    // links are used.
+    /** @var \Behat\Mink\Element\NodeElement[] $rows */
+    $rows = $page->findAll('css', 'tbody tr');
+    $this->assertCount(5, $rows);
+    $this->assertStringContainsString('Node 1 content default_value', $rows[0]->getHtml());
+
+    $this->clickLink('Go to page 2');
+    $session_assert->assertWaitOnAjaxRequest();
+    $rows = $page->findAll('css', 'tbody tr');
+    $this->assertCount(1, $rows);
+    $this->assertStringContainsString('Node 6 content default_value', $rows[0]->getHtml());
+    $link = $page->findLink('Go to page 1');
+    $this->assertNoDuplicateAssetsOnPage();
+
+    // Test that no unwanted parameters are added to the URL.
+    $this->assertEquals('?status=All&type=All&title=default_value&langcode=All&items_per_page=5&order=changed&sort=asc&page=0', $link->getAttribute('href'));
+
+    // Set the title filter to empty string using the exposed pager.
+    $page->fillField('title', '');
+    $page->pressButton('Filter');
+    $session_assert->assertWaitOnAjaxRequest();
+    $rows = $page->findAll('css', 'tbody tr');
+    $this->assertCount(5, $rows);
+    $this->assertStringContainsString('Node 11 content', $rows[0]->getHtml());
+
+    // Navigate to the second page.
+    $this->clickLink('Go to page 2');
+    $session_assert->assertWaitOnAjaxRequest();
+    $rows = $page->findAll('css', 'tbody tr');
+    $this->assertCount(5, $rows);
+    $this->assertStringContainsString('Node 6 content default_value', $rows[0]->getHtml());
+    $link = $page->findLink('Go to page 1');
+    $this->assertNoDuplicateAssetsOnPage();
+
+    // Test that no unwanted parameters are added to the URL.
+    $this->assertEquals('?status=All&type=All&title=&langcode=All&items_per_page=5&page=0', $link->getAttribute('href'));
+
+    // Navigate back to the first page.
+    $this->clickLink('Go to first page');
+    $session_assert->assertWaitOnAjaxRequest();
+    $rows = $page->findAll('css', 'tbody tr');
+    $this->assertCount(5, $rows);
+    $this->assertStringContainsString('Node 11 content', $rows[0]->getHtml());
+
+    // Navigate using the 'next' link.
+    $this->clickLink('Go to next page');
+    $session_assert->assertWaitOnAjaxRequest();
+    $rows = $page->findAll('css', 'tbody tr');
+    $this->assertCount(5, $rows);
+    $this->assertStringContainsString('Node 6 content default_value', $rows[0]->getHtml());
+
+    // Navigate using the 'last' link.
+    $this->clickLink('Go to last page');
+    $session_assert->assertWaitOnAjaxRequest();
+    $rows = $page->findAll('css', 'tbody tr');
+    $this->assertCount(1, $rows);
+    $this->assertStringContainsString('Node 1 content default_value', $rows[0]->getHtml());
+
+    // Make sure the AJAX calls don't change the view_path.
+    $settings = $this->getDrupalSettings();
+    $this->assertEquals($expected_view_path, current($settings['views']['ajaxViews'])['view_path']);
+  }
+
   /**
    * Assert that assets are not loaded twice on a page.
    *
diff --git a/core/modules/views/tests/src/Kernel/Entity/ViewValidationTest.php b/core/modules/views/tests/src/Kernel/Entity/ViewValidationTest.php
index 94bc6023fc8bd6e2d67d3b7eb9207c14849fbaa3..e36eb7471b756c96ce4d5cb02fbef66d3650bbd1 100644
--- a/core/modules/views/tests/src/Kernel/Entity/ViewValidationTest.php
+++ b/core/modules/views/tests/src/Kernel/Entity/ViewValidationTest.php
@@ -30,4 +30,13 @@ protected function setUp(): void {
     $this->entity->save();
   }
 
+  /**
+   * @group legacy
+   */
+  public function testLabelsAreRequired(): void {
+    $this->entity->set('label', NULL);
+    $this->expectDeprecation('Saving a view without an explicit label is deprecated in drupal:10.2.0 and will raise an error in drupal:11.0.0. See https://www.drupal.org/node/3381669');
+    $this->assertSame($this->entity->id(), $this->entity->label());
+  }
+
 }
diff --git a/core/modules/views/tests/src/Kernel/EventSubscriber/ViewsEntitySchemaSubscriberIntegrationTest.php b/core/modules/views/tests/src/Kernel/EventSubscriber/ViewsEntitySchemaSubscriberIntegrationTest.php
index 8f7f5307359c69a28bdf2a38c361ed1069705099..c5db2717eae179fd9931f1746089c78d33c2a4dd 100644
--- a/core/modules/views/tests/src/Kernel/EventSubscriber/ViewsEntitySchemaSubscriberIntegrationTest.php
+++ b/core/modules/views/tests/src/Kernel/EventSubscriber/ViewsEntitySchemaSubscriberIntegrationTest.php
@@ -12,6 +12,7 @@
  * Tests \Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber.
  *
  * @group Views
+ * @group #slow
  */
 class ViewsEntitySchemaSubscriberIntegrationTest extends ViewsKernelTestBase {
 
diff --git a/core/modules/views/tests/src/Kernel/Handler/FieldDropbuttonTest.php b/core/modules/views/tests/src/Kernel/Handler/FieldDropbuttonTest.php
index 693d78f6897e6929f59021a28cb96d983d3ce30a..5c6ad2913d4bea41be5d7835560e95112480432b 100644
--- a/core/modules/views/tests/src/Kernel/Handler/FieldDropbuttonTest.php
+++ b/core/modules/views/tests/src/Kernel/Handler/FieldDropbuttonTest.php
@@ -96,7 +96,7 @@ protected function setUp($import_test_views = TRUE): void {
     ]);
     $this->node2 = $this->createNode([
       'type' => 'foo',
-      'title' => 'foos',
+      'title' => 'foo',
       'status' => 1,
       'uid' => $admin->id(),
       'created' => REQUEST_TIME - 5,
diff --git a/core/modules/views/tests/src/Kernel/Handler/FieldFieldAccessTestBase.php b/core/modules/views/tests/src/Kernel/Handler/FieldFieldAccessTestBase.php
index b963c8d58c270528cad55bf5065aa6e9d2a958a3..7155b25d824978d0f42a86887ce519a3d93f9d92 100644
--- a/core/modules/views/tests/src/Kernel/Handler/FieldFieldAccessTestBase.php
+++ b/core/modules/views/tests/src/Kernel/Handler/FieldFieldAccessTestBase.php
@@ -93,6 +93,7 @@ protected function assertFieldAccess($entity_type_id, $field_name, $field_conten
     $base_table = ($data_table && ($field_name !== 'uuid')) ? $data_table : $entity_type->getBaseTable();
     $entity = View::create([
       'id' => $view_id,
+      'label' => $view_id,
       'base_table' => $base_table,
       'display' => [
         'default' => [
diff --git a/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php b/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php
index d893234e4db3e70781c627d90cde99791dc92c1e..458cab3e06f23000dd995d732bef787d878b5711 100644
--- a/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php
+++ b/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php
@@ -18,6 +18,7 @@
  *
  * @see \Drupal\views\Plugin\views\field\EntityField
  * @group views
+ * @group #slow
  */
 class FieldFieldTest extends ViewsKernelTestBase {
 
diff --git a/core/modules/views/tests/src/Kernel/Handler/FilterNumericTest.php b/core/modules/views/tests/src/Kernel/Handler/FilterNumericTest.php
index cfef9d458cd79ac6bf4f93e223f8c60156aa1287..ab7cef8c83903b4177ae7329bdd4d52a1f378400 100644
--- a/core/modules/views/tests/src/Kernel/Handler/FilterNumericTest.php
+++ b/core/modules/views/tests/src/Kernel/Handler/FilterNumericTest.php
@@ -9,6 +9,7 @@
  * Tests the numeric filter handler.
  *
  * @group views
+ * @group #slow
  */
 class FilterNumericTest extends ViewsKernelTestBase {
 
diff --git a/core/modules/views/tests/src/Kernel/Handler/FilterStringTest.php b/core/modules/views/tests/src/Kernel/Handler/FilterStringTest.php
index 45f443af95341a421e0bd0b635ec95ca3a6b1ad0..b36a34d22205fddf20cec3b09bdd499d8b219072 100644
--- a/core/modules/views/tests/src/Kernel/Handler/FilterStringTest.php
+++ b/core/modules/views/tests/src/Kernel/Handler/FilterStringTest.php
@@ -9,6 +9,7 @@
  * Tests the core Drupal\views\Plugin\views\filter\StringFilter handler.
  *
  * @group views
+ * @group #slow
  */
 class FilterStringTest extends ViewsKernelTestBase {
 
diff --git a/core/modules/views/tests/src/Kernel/QueryGroupByTest.php b/core/modules/views/tests/src/Kernel/QueryGroupByTest.php
index 20a55769fbd540b6bc4d9809fd024d2a30b787d1..522ff701ca66122237114d6dfaff3052b070ccfc 100644
--- a/core/modules/views/tests/src/Kernel/QueryGroupByTest.php
+++ b/core/modules/views/tests/src/Kernel/QueryGroupByTest.php
@@ -13,6 +13,7 @@
  * Tests aggregate functionality of views, for example count.
  *
  * @group views
+ * @group #slow
  */
 class QueryGroupByTest extends ViewsKernelTestBase {
 
diff --git a/core/modules/views/tests/src/Kernel/ViewExecutableTest.php b/core/modules/views/tests/src/Kernel/ViewExecutableTest.php
index f44cdc15a0d0a8848976a5fe0b62a648c6a9e93f..4e6d651b1877b616f37c4756383212ce4d9f4f64 100644
--- a/core/modules/views/tests/src/Kernel/ViewExecutableTest.php
+++ b/core/modules/views/tests/src/Kernel/ViewExecutableTest.php
@@ -27,6 +27,7 @@
  * Tests the ViewExecutable class.
  *
  * @group views
+ * @group #slow
  * @see \Drupal\views\ViewExecutable
  */
 class ViewExecutableTest extends ViewsKernelTestBase {
diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php
index 1d3c33e4d521bad4e4a0c487f48cdb7eaf114b6b..766e5630ba800a4cff7d99640d01a6ae46eae43c 100644
--- a/core/modules/views/views.post_update.php
+++ b/core/modules/views/views.post_update.php
@@ -95,6 +95,17 @@ function views_post_update_fix_revision_id_part(&$sandbox = NULL): void {
     });
 }
 
+/**
+ * Add labels to views which don't have one.
+ */
+function views_post_update_add_missing_labels(&$sandbox = NULL): void {
+  /** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */
+  $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool {
+    return $view_config_updater->addLabelIfMissing($view);
+  });
+}
+
 /**
  * Remove the skip_cache settings.
  */
@@ -115,3 +126,14 @@ function views_post_update_remove_default_argument_skip_url(array &$sandbox = NU
     return $view_config_updater->needsDefaultArgumentSkipUrlUpdate($view);
   });
 }
+
+/**
+ * Removes User context from views with taxonomy filters.
+ */
+function views_post_update_taxonomy_filter_user_context(?array &$sandbox = NULL): void {
+  /** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */
+  $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class);
+  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater): bool {
+    return $view_config_updater->needsTaxonomyTermFilterUpdate($view);
+  });
+}
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index 948f459065edd523ee29e9f05ea8b59777f166f9..be6bfee2e4b9c9ec0c0a750259072cf9d565a22c 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -501,19 +501,19 @@ function template_preprocess_views_view_table(&$variables) {
 
       $variables['header'][$field]['default_classes'] = $fields[$field]->options['element_default_classes'];
       // Set up the header label class.
-      $variables['header'][$field]['attributes'] = [];
+      $variables['header'][$field]['attributes'] = new Attribute();
       $class = $fields[$field]->elementLabelClasses(0);
       if ($class) {
-        $variables['header'][$field]['attributes']['class'][] = $class;
+        $variables['header'][$field]['attributes']->addClass($class);
       }
       // Add responsive header classes.
       if (!empty($options['info'][$field]['responsive'])) {
-        $variables['header'][$field]['attributes']['class'][] = $options['info'][$field]['responsive'];
+        $variables['header'][$field]['attributes']->addClass($options['info'][$field]['responsive']);
         $responsive = TRUE;
       }
       // Add a CSS align class to each field if one was set.
       if (!empty($options['info'][$field]['align'])) {
-        $variables['header'][$field]['attributes']['class'][] = Html::cleanCssIdentifier($options['info'][$field]['align']);
+        $variables['header'][$field]['attributes']->addClass(Html::cleanCssIdentifier($options['info'][$field]['align']));
       }
       // Add a header label wrapper if one was selected.
       if ($variables['header'][$field]['content']) {
@@ -522,7 +522,7 @@ function template_preprocess_views_view_table(&$variables) {
           $variables['header'][$field]['wrapper_element'] = $element_label_type;
         }
         // Improves accessibility of complex tables.
-        $variables['header'][$field]['attributes']['id'] = Html::getUniqueId('view-' . $field . '-table-column');
+        $variables['header'][$field]['attributes']->setAttribute('id', Html::getUniqueId('view-' . $field . '-table-column'));
       }
       // aria-sort is a WAI-ARIA property that indicates if items in a table
       // or grid are sorted in ascending or descending order. See
@@ -535,8 +535,6 @@ function template_preprocess_views_view_table(&$variables) {
       if (!empty($variables['header'][$field]['content'])) {
         $has_header_labels = TRUE;
       }
-
-      $variables['header'][$field]['attributes'] = new Attribute($variables['header'][$field]['attributes']);
     }
 
     // Add a CSS align class to each field if one was set.
@@ -564,21 +562,24 @@ function template_preprocess_views_view_table(&$variables) {
 
       // Add field classes.
       if (!isset($column_reference['attributes'])) {
-        $column_reference['attributes'] = [];
+        $column_reference['attributes'] = new Attribute();
+      }
+      elseif (!($column_reference['attributes'] instanceof Attribute)) {
+        $column_reference['attributes'] = new Attribute($column_reference['attributes']);
       }
 
       if ($classes = $fields[$field]->elementClasses($num)) {
-        $column_reference['attributes']['class'][] = $classes;
+        $column_reference['attributes']->addClass(preg_split('/\s+/', $classes));
       }
 
       // Add responsive header classes.
       if (!empty($options['info'][$field]['responsive'])) {
-        $column_reference['attributes']['class'][] = $options['info'][$field]['responsive'];
+        $column_reference['attributes']->addClass($options['info'][$field]['responsive']);
       }
 
       // Improves accessibility of complex tables.
       if (isset($variables['header'][$field]['attributes']['id'])) {
-        $column_reference['attributes']['headers'] = [$variables['header'][$field]['attributes']['id']];
+        $column_reference['attributes']->setAttribute('headers', [$variables['header'][$field]['attributes']['id']]);
       }
 
       if (!empty($fields[$field])) {
@@ -604,7 +605,6 @@ function template_preprocess_views_view_table(&$variables) {
           }
         }
       }
-      $column_reference['attributes'] = new Attribute($column_reference['attributes']);
     }
 
     // Remove columns if the "empty_column" option is checked and the
diff --git a/core/modules/views_ui/src/Form/BasicSettingsForm.php b/core/modules/views_ui/src/Form/BasicSettingsForm.php
index 92f91ddfbb66dba056decbcf30372ae2785d0658..e45bca2cbf7d3024d99b7f9392a9be38ec7828b5 100644
--- a/core/modules/views_ui/src/Form/BasicSettingsForm.php
+++ b/core/modules/views_ui/src/Form/BasicSettingsForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\views_ui\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
@@ -27,11 +28,13 @@ class BasicSettingsForm extends ConfigFormBase {
    *
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The factory for configuration objects.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
+   *   The typed config manager.
    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
    *   The theme handler.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, ThemeHandlerInterface $theme_handler) {
-    parent::__construct($config_factory);
+  public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ThemeHandlerInterface $theme_handler) {
+    parent::__construct($config_factory, $typedConfigManager);
 
     $this->themeHandler = $theme_handler;
   }
@@ -42,6 +45,7 @@ public function __construct(ConfigFactoryInterface $config_factory, ThemeHandler
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('config.factory'),
+      $container->get('config.typed'),
       $container->get('theme_handler')
     );
   }
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index 58eafbd55ff0e51d4745965102bb98dc6efdb72f..636cac7c5ab68b42c6500fc73ece42c897feb5fe 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -1004,7 +1004,7 @@ public function save() {
   /**
    * {@inheritdoc}
    */
-  public function toUrl($rel = 'edit-form', array $options = []) {
+  public function toUrl($rel = NULL, array $options = []) {
     return $this->storage->toUrl($rel, $options);
   }
 
diff --git a/core/modules/views_ui/tests/src/Functional/GenericTest.php b/core/modules/views_ui/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..018aec820950e3568cd1186501b35fbf68e07306
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\views_ui\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for views_ui.
+ *
+ * @group views_ui
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/views_ui/tests/src/Functional/ViewsListTest.php b/core/modules/views_ui/tests/src/Functional/ViewsListTest.php
index 2bbfbdd8b927810a084d5b1261e4c1445b03070d..7042af847e4a6eccae325ee7031bf0f7742b7f41 100644
--- a/core/modules/views_ui/tests/src/Functional/ViewsListTest.php
+++ b/core/modules/views_ui/tests/src/Functional/ViewsListTest.php
@@ -66,7 +66,7 @@ public function testViewsListLimit() {
     $limit = 51;
     $values = $this->config('views.view.test_view_storage')->get();
     for ($i = 1; $i <= $limit - $views; $i++) {
-      $values['id'] = 'test_view_storage_new' . $i;
+      $values['id'] = $values['label'] = 'test_view_storage_new' . $i;
       unset($values['uuid']);
       $created = View::create($values);
       $created->save();
diff --git a/core/modules/views_ui/tests/src/Kernel/TagTest.php b/core/modules/views_ui/tests/src/Kernel/TagTest.php
index 4fa23c5c05e49ef9ecaf6b228e43d316a707f1ee..cc85799e46c6a86b1beb8cc7f03eb76b6c99ddbc 100644
--- a/core/modules/views_ui/tests/src/Kernel/TagTest.php
+++ b/core/modules/views_ui/tests/src/Kernel/TagTest.php
@@ -33,7 +33,11 @@ public function testViewsUiAutocompleteTag() {
       $suffix = $i % 2 ? 'odd' : 'even';
       $tag = 'autocomplete_tag_test_' . $suffix . $this->randomMachineName();
       $tags[] = $tag;
-      View::create(['tag' => $tag, 'id' => $this->randomMachineName()])->save();
+      View::create([
+        'tag' => $tag,
+        'id' => $this->randomMachineName(),
+        'label' => 'Test',
+      ])->save();
     }
 
     // Make sure just ten results are returned.
@@ -77,7 +81,11 @@ public function testViewsUiAutocompleteIndividualTags($expected_tag, $search_str
     $controller = ViewsUIController::create($this->container);
     $request = $this->container->get('request_stack')->getCurrentRequest();
     $tag = 'comma, 你好, Foo bar';
-    View::create(['tag' => $tag, 'id' => $this->randomMachineName()])->save();
+    View::create([
+      'tag' => $tag,
+      'id' => $this->randomMachineName(),
+      'label' => 'Test',
+    ])->save();
     $request->query->set('q', $search_string);
     $result = $controller->autocompleteTag($request);
     $matches = (array) json_decode($result->getContent());
diff --git a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
index b576cb7b01a8d42fbbf691c6d62673428286bbd4..0c7348a21f650d4e41edf035613c0c34d25cc1c1 100644
--- a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
+++ b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Menu\MenuParentFormSelector;
 use Drupal\Tests\UnitTestCase;
 use Drupal\views\Entity\View;
@@ -102,6 +103,7 @@ public function testBuildRowEntityList() {
       ->getMock();
 
     $values = [];
+    $values['label'] = 'Test';
     $values['status'] = FALSE;
     $values['display']['default']['id'] = 'default';
     $values['display']['default']['display_title'] = 'Display';
@@ -145,13 +147,16 @@ public function testBuildRowEntityList() {
       ->getMock();
     $route_provider = $this->createMock('Drupal\Core\Routing\RouteProviderInterface');
     $executable_factory = new ViewExecutableFactory($user, $request_stack, $views_data, $route_provider);
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
     $container->set('views.executable', $executable_factory);
     $container->set('plugin.manager.views.display', $display_manager);
+    $container->set('entity_type.manager', $entity_type_manager->reveal());
     \Drupal::setContainer($container);
 
     // Setup a view list builder with a mocked buildOperations method,
     // because t() is called on there.
     $entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface');
+    $entity_type_manager->getDefinition('view')->willReturn($entity_type);
     $view_list_builder = new TestViewListBuilder($entity_type, $storage, $display_manager);
     $view_list_builder->setStringTranslation($this->getStringTranslationStub());
 
diff --git a/core/modules/workflows/tests/src/Functional/GenericTest.php b/core/modules/workflows/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4cf60632872fa4589cc272eddb9a191f14a54ad8
--- /dev/null
+++ b/core/modules/workflows/tests/src/Functional/GenericTest.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Drupal\Tests\workflows\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for workflows.
+ *
+ * @group workflows
+ */
+class GenericTest extends GenericModuleTestBase {}
diff --git a/core/modules/workflows/tests/src/Kernel/WorkflowAccessControlHandlerTest.php b/core/modules/workflows/tests/src/Kernel/WorkflowAccessControlHandlerTest.php
index 3b1ae3426531f33b2bc9660d9c549bcc693657b3..df8496182127c8a5fb98f473bb4358e41fef8e17 100644
--- a/core/modules/workflows/tests/src/Kernel/WorkflowAccessControlHandlerTest.php
+++ b/core/modules/workflows/tests/src/Kernel/WorkflowAccessControlHandlerTest.php
@@ -13,6 +13,7 @@
 /**
  * @coversDefaultClass \Drupal\workflows\WorkflowAccessControlHandler
  * @group workflows
+ * @group #slow
  */
 class WorkflowAccessControlHandlerTest extends KernelTestBase {
 
diff --git a/core/modules/workflows/tests/src/Kernel/WorkflowValidationTest.php b/core/modules/workflows/tests/src/Kernel/WorkflowValidationTest.php
index a3448a1a10cad69564d79631d9e3c477503a2710..5f1c558e893fe0b2b494939a62bc8c69fd17c2a9 100644
--- a/core/modules/workflows/tests/src/Kernel/WorkflowValidationTest.php
+++ b/core/modules/workflows/tests/src/Kernel/WorkflowValidationTest.php
@@ -9,6 +9,7 @@
  * Tests validation of workflow entities.
  *
  * @group workflows
+ * @group #slow
  */
 class WorkflowValidationTest extends ConfigEntityValidationTestBase {
 
diff --git a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php
index ac053eba8cb8b83c79e53074b2505355896c2231..64ddf9238399ba209c224545ef31bf3359a6a9de 100644
--- a/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php
+++ b/core/modules/workspaces/src/Plugin/Validation/Constraint/EntityWorkspaceConflictConstraintValidator.php
@@ -86,6 +86,7 @@ public function validate($entity, Constraint $constraint) {
       // Get the latest revision of the entity in order to check if it's being
       // edited in a different workspace.
       $latest_revision = $this->workspaceManager->executeOutsideWorkspace(function () use ($entity) {
+        /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
         $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
         return $storage->loadRevision($storage->getLatestRevisionId($entity->id()));
       });
diff --git a/core/modules/workspaces/src/WorkspaceManager.php b/core/modules/workspaces/src/WorkspaceManager.php
index f48d1c805d59f3edb6611c26da310d7b514415d9..e6663f7ea74962740e8d4f546bc93a3d08cfbbca 100644
--- a/core/modules/workspaces/src/WorkspaceManager.php
+++ b/core/modules/workspaces/src/WorkspaceManager.php
@@ -325,6 +325,7 @@ public function purgeDeletedWorkspacesBatch() {
 
     $count = 1;
     foreach ($all_associated_revisions as $entity_type_id => $associated_revisions) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $associated_entity_storage */
       $associated_entity_storage = $this->entityTypeManager->getStorage($entity_type_id);
 
       // Sort the associated revisions in reverse ID order, so we can delete the
diff --git a/core/modules/workspaces/src/WorkspaceOperationFactory.php b/core/modules/workspaces/src/WorkspaceOperationFactory.php
index 1f4474cd4e544280fb6225eb3948291f249f6b15..4346763c62f4405ac139131ff338f150fce21318 100644
--- a/core/modules/workspaces/src/WorkspaceOperationFactory.php
+++ b/core/modules/workspaces/src/WorkspaceOperationFactory.php
@@ -85,7 +85,7 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Con
     $this->workspaceAssociation = $workspace_association;
     $this->cacheTagsInvalidator = $cache_tags_invalidator;
     if (!$event_dispatcher) {
-      @trigger_error('The event dispatcher service should be passed to WorkspaceOperationFactory::__construct() since 10.1.0. This will be required in Drupal 11.0.0.', E_USER_DEPRECATED);
+      @trigger_error('Calling ' . __METHOD__ . '() without the $event_dispatcher argument is deprecated in drupal:10.1.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3242573', E_USER_DEPRECATED);
       $event_dispatcher = \Drupal::service('event_dispatcher');
     }
     $this->eventDispatcher = $event_dispatcher;
diff --git a/core/modules/workspaces/tests/src/Functional/GenericTest.php b/core/modules/workspaces/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7ca8f7d54f7f212aa3185f1db0b3870b42058fb4
--- /dev/null
+++ b/core/modules/workspaces/tests/src/Functional/GenericTest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Tests\workspaces\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for workspaces.
+ *
+ * @group workspaces
+ */
+class GenericTest extends GenericModuleTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function preUninstallSteps(): void {
+    $storage = \Drupal::entityTypeManager()->getStorage('workspace');
+    $workspaces = $storage->loadMultiple();
+    $storage->delete($workspaces);
+  }
+
+}
diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceBypassTest.php b/core/modules/workspaces/tests/src/Functional/WorkspaceBypassTest.php
index ed0496e405ab126c136a7a1e55df8dc499fc98d7..e4614d7d4a737c470f9426513255d4b8262f4544 100644
--- a/core/modules/workspaces/tests/src/Functional/WorkspaceBypassTest.php
+++ b/core/modules/workspaces/tests/src/Functional/WorkspaceBypassTest.php
@@ -39,19 +39,19 @@ public function testBypassOwnWorkspace() {
     $this->createContentType(['type' => 'test', 'label' => 'Test']);
     $this->setupWorkspaceSwitcherBlock();
 
-    $ditka = $this->drupalCreateUser(array_merge($permissions, ['create test content']));
+    $coach = $this->drupalCreateUser(array_merge($permissions, ['create test content']));
 
     // Login as a limited-access user and create a workspace.
-    $this->drupalLogin($ditka);
+    $this->drupalLogin($coach);
     $bears = $this->createWorkspaceThroughUi('Bears', 'bears');
     $this->switchToWorkspace($bears);
 
     // Now create a node in the Bears workspace, as the owner of that workspace.
-    $ditka_bears_node = $this->createNodeThroughUi('Ditka Bears node', 'test');
-    $ditka_bears_node_id = $ditka_bears_node->id();
+    $coach_bears_node = $this->createNodeThroughUi('Ditka Bears node', 'test');
+    $coach_bears_node_id = $coach_bears_node->id();
 
     // Editing both nodes should be possible.
-    $this->drupalGet('/node/' . $ditka_bears_node_id . '/edit');
+    $this->drupalGet('/node/' . $coach_bears_node_id . '/edit');
     $this->assertSession()->statusCodeEquals(200);
 
     // Create a new user that should be able to edit anything in the Bears
@@ -63,7 +63,7 @@ public function testBypassOwnWorkspace() {
 
     // Editor 2 has the bypass permission but does not own the workspace and so,
     // should not be able to create and edit any node.
-    $this->drupalGet('/node/' . $ditka_bears_node_id . '/edit');
+    $this->drupalGet('/node/' . $coach_bears_node_id . '/edit');
     $this->assertSession()->statusCodeEquals(403);
   }
 
diff --git a/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php b/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php
index f8080e8f76a9dd2a9f418253aeb6d36baabf1c07..524413750b58a639c7aaa3482a556ce7bc73a4c5 100644
--- a/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php
+++ b/core/modules/workspaces/tests/src/Functional/WorkspaceTest.php
@@ -11,6 +11,7 @@
  * Test the workspace entity.
  *
  * @group workspaces
+ * @group #slow
  */
 class WorkspaceTest extends BrowserTestBase {
 
diff --git a/core/package.json b/core/package.json
index 321ecdb74a825d89312d7cba3e0ad11d0f7c0011..cb162cfb536c32d01d4f7e644a20efeb7db32761 100644
--- a/core/package.json
+++ b/core/package.json
@@ -22,7 +22,7 @@
     "prettier": "prettier --write \"./**/*.js\"",
     "spellcheck": "cspell -c .cspell.json",
     "spellcheck:make-drupal-dict": "rm -f misc/cspell/dictionary.txt && touch misc/cspell/dictionary.txt && yarn -s spellcheck:core --unique --words-only | perl -Mopen=locale -pe '$_=lc$_' | LC_ALL=en_US.UTF-8 tr -d \\\\\\\\ | LC_ALL=C sort -u -o misc/cspell/dictionary.txt",
-    "spellcheck:core": "cspell -c .cspell.json --no-progress --root .. \"core/**/*\" \"composer/**/*\" \"composer.json\"",
+    "spellcheck:core": "cspell -c .cspell.json --root .. \"core/**/*\" \"composer/**/*\" \"composer.json\" \".gitlab-ci/*\" \".gitlab-ci.yml\"",
     "vendor-update": "node ./scripts/js/vendor-update.js",
     "watch:ckeditor5": "webpack --config ./modules/ckeditor5/webpack.config.js --watch",
     "build:ckeditor5": "webpack --config ./modules/ckeditor5/webpack.config.js",
@@ -63,6 +63,7 @@
     "eslint": "^8.44.0",
     "eslint-config-airbnb-base": "^15.0.0",
     "eslint-config-prettier": "^8.4.0",
+    "eslint-formatter-gitlab": "^5.0.0",
     "eslint-plugin-import": "^2.25.4",
     "eslint-plugin-jquery": "^1.5.1",
     "eslint-plugin-prettier": "^4.0.0",
@@ -90,18 +91,20 @@
     "stylelint": "^15.10.1",
     "stylelint-checkstyle-formatter": "^0.1.2",
     "stylelint-config-standard": "^33.0.0",
+    "stylelint-formatter-gitlab": "^1.0.2",
     "stylelint-order": "^6.0.3",
     "tabbable": "^6.1.2",
     "terser": "^5.19.0",
     "terser-webpack-plugin": "^5.3.9",
-    "tua-body-scroll-lock": "^1.4.0",
     "transliteration": "^2.3.5",
+    "tua-body-scroll-lock": "^1.4.0",
     "underscore": "~1.13.4",
     "webpack": "^5.85.1",
     "webpack-cli": "^5.1.3"
   },
   "resolutions": {
-    "nightwatch/semver": "~7.5.2"
+    "nightwatch/semver": "~7.5.2",
+    "get-func-name": "^2.0.2"
   },
   "browserslist": [
     "last 2 Chrome major versions",
diff --git a/core/phpcs.xml.dist b/core/phpcs.xml.dist
index 0f1c01619ffc85c930692f2f941d6aae6909632e..ed5acf2e732dfeb6f57a9534457f0a52049e2ede 100644
--- a/core/phpcs.xml.dist
+++ b/core/phpcs.xml.dist
@@ -123,6 +123,7 @@
   <rule ref="Drupal.Semantics.FunctionT">
     <exclude name="Drupal.Semantics.FunctionT.NotLiteralString"/>
   </rule>
+  <rule ref="Drupal.Semantics.FunctionTriggerError"/>
   <rule ref="Drupal.Semantics.FunctionWatchdog"/>
   <rule ref="Drupal.Semantics.InstallHooks"/>
   <rule ref="Drupal.Semantics.LStringTranslatable"/>
diff --git a/core/phpstan-baseline.neon b/core/phpstan-baseline.neon
index a3b1a567790871bd4ef23eab38c444d1060a8ff2..6fb89a4d5afe4fce2c49295e77d863f04174fb0c 100644
--- a/core/phpstan-baseline.neon
+++ b/core/phpstan-baseline.neon
@@ -20,6 +20,15 @@ parameters:
 			count: 1
 			path: includes/form.inc
 
+		-
+			message: """
+				#^Call to deprecated method getFromDriverName\\(\\) of class Drupal\\\\Core\\\\Extension\\\\DatabaseDriverList\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
+				  DatabaseDriverList\\:\\:get\\(\\) instead, passing a database driver namespace\\.$#
+			"""
+			count: 1
+			path: includes/install.core.inc
+
 		-
 			message: "#^Function install_config_download_translations\\(\\) should return string but return statement is missing\\.$#"
 			count: 1
@@ -40,6 +49,15 @@ parameters:
 			count: 1
 			path: includes/theme.maintenance.inc
 
+		-
+			message: """
+				#^Call to deprecated method registerLoader\\(\\) of class Doctrine\\\\Common\\\\Annotations\\\\AnnotationRegistry\\:
+				This method is deprecated and will be removed in
+				            doctrine/annotations 2\\.0\\. Annotations will be autoloaded in 2\\.0\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php
+
 		-
 			message: "#^Call to an undefined method Drupal\\\\Component\\\\Datetime\\\\DateTimePlus\\:\\:getTimeZone\\(\\)\\.$#"
 			count: 1
@@ -55,6 +73,15 @@ parameters:
 			count: 1
 			path: lib/Drupal/Component/Datetime/Time.php
 
+		-
+			message: """
+				#^Usage of deprecated trait Drupal\\\\Component\\\\DependencyInjection\\\\ServiceIdHashTrait in class Drupal\\\\Component\\\\DependencyInjection\\\\Container\\:
+				in drupal\\:9\\.5\\.1 and is removed from drupal\\:11\\.0\\.0\\. Use the
+				  'Drupal\\\\Component\\\\DependencyInjection\\\\ReverseContainer' service instead\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Component/DependencyInjection/Container.php
+
 		-
 			message: "#^Variable \\$x0 might not be defined\\.$#"
 			count: 4
@@ -255,6 +282,15 @@ parameters:
 			count: 1
 			path: lib/Drupal/Core/Condition/ConditionManager.php
 
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 4
+			path: lib/Drupal/Core/Config/DatabaseStorage.php
+
 		-
 			message: "#^Variable \\$value in isset\\(\\) always exists and is not nullable\\.$#"
 			count: 2
@@ -270,21 +306,120 @@ parameters:
 			count: 1
 			path: lib/Drupal/Core/Config/TypedConfigManager.php
 
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Connection.php
+
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_INSERT_ID of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Connection.php
+
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_NULL of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Connection.php
+
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_STATEMENT of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 2
+			path: lib/Drupal/Core/Database/Connection.php
+
 		-
 			message: "#^Variable \\$statement might not be defined\\.$#"
 			count: 1
 			path: lib/Drupal/Core/Database/Connection.php
 
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Query/Delete.php
+
 		-
 			message: "#^Method Drupal\\\\Core\\\\Database\\\\Query\\\\Delete\\:\\:execute\\(\\) should return int but return statement is missing\\.$#"
 			count: 1
 			path: lib/Drupal/Core/Database/Query/Delete.php
 
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_INSERT_ID of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Query/Insert.php
+
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Query/Merge.php
+
 		-
 			message: "#^Method Drupal\\\\Core\\\\Database\\\\Query\\\\Merge\\:\\:__toString\\(\\) should return string but return statement is missing\\.$#"
 			count: 1
 			path: lib/Drupal/Core/Database/Query/Merge.php
 
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_STATEMENT of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Query/Select.php
+
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Query/Truncate.php
+
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Query/Update.php
+
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Database/Query/Upsert.php
+
 		-
 			message: "#^Variable \\$affected_rows might not be defined\\.$#"
 			count: 1
@@ -295,6 +430,33 @@ parameters:
 			count: 2
 			path: lib/Drupal/Core/Datetime/DateHelper.php
 
+		-
+			message: """
+				#^Usage of deprecated trait Drupal\\\\Component\\\\DependencyInjection\\\\ServiceIdHashTrait in class Drupal\\\\Core\\\\DependencyInjection\\\\ContainerBuilder\\:
+				in drupal\\:9\\.5\\.1 and is removed from drupal\\:11\\.0\\.0\\. Use the
+				  'Drupal\\\\Component\\\\DependencyInjection\\\\ReverseContainer' service instead\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
+
+		-
+			message: """
+				#^Call to deprecated method closing\\(\\) of class Drupal\\\\Component\\\\Diff\\\\WordLevelDiff\\:
+				in drupal\\:10\\.1\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Diff/DiffFormatter.php
+
+		-
+			message: """
+				#^Call to deprecated method orig\\(\\) of class Drupal\\\\Component\\\\Diff\\\\WordLevelDiff\\:
+				in drupal\\:10\\.1\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Diff/DiffFormatter.php
+
 		-
 			message: "#^Method Drupal\\\\Core\\\\DrupalKernel\\:\\:discoverServiceProviders\\(\\) should return array but return statement is missing\\.$#"
 			count: 1
@@ -360,11 +522,6 @@ parameters:
 			count: 1
 			path: lib/Drupal/Core/Entity/EntityConfirmFormBase.php
 
-		-
-			message: "#^Variable \\$selection in empty\\(\\) always exists and is not falsy\\.$#"
-			count: 1
-			path: lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php
-
 		-
 			message: "#^Access to an undefined property Drupal\\\\Core\\\\Entity\\\\EntityDisplayBase\\:\\:\\$_serializedKeys\\.$#"
 			count: 2
@@ -410,6 +567,15 @@ parameters:
 			count: 5
 			path: lib/Drupal/Core/Entity/Query/Sql/Tables.php
 
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_INSERT_ID of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 2
+			path: lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+
 		-
 			message: "#^Variable \\$revision_query might not be defined\\.$#"
 			count: 2
@@ -440,6 +606,15 @@ parameters:
 			count: 2
 			path: lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
 
+		-
+			message: """
+				#^Call to deprecated method getFromDriverName\\(\\) of class Drupal\\\\Core\\\\Extension\\\\DatabaseDriverList\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
+				  DatabaseDriverList\\:\\:get\\(\\) instead, passing a database driver namespace\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Extension/DatabaseDriverList.php
+
 		-
 			message: "#^Variable \\$minor_version might not be defined\\.$#"
 			count: 1
@@ -605,6 +780,15 @@ parameters:
 			count: 1
 			path: lib/Drupal/Core/Menu/MenuLinkManager.php
 
+		-
+			message: """
+				#^Fetching deprecated class constant RETURN_INSERT_ID of class Drupal\\\\Core\\\\Database\\\\Database\\:
+				in drupal\\:9\\.4\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is no
+				  replacement\\.$#
+			"""
+			count: 1
+			path: lib/Drupal/Core/Menu/MenuTreeStorage.php
+
 		-
 			message: "#^Variable \\$transaction in isset\\(\\) always exists and is not nullable\\.$#"
 			count: 1
@@ -900,16 +1084,6 @@ parameters:
 			count: 1
 			path: modules/config_translation/src/FormElement/DateFormat.php
 
-		-
-			message: "#^Variable \\$settings_locations in empty\\(\\) always exists and is not falsy\\.$#"
-			count: 1
-			path: modules/config_translation/tests/src/Functional/ConfigTranslationUiTest.php
-
-		-
-			message: "#^Variable \\$source_string in empty\\(\\) always exists and is not falsy\\.$#"
-			count: 1
-			path: modules/config_translation/tests/src/Functional/ConfigTranslationUiTest.php
-
 		-
 			message: "#^Method Drupal\\\\contact\\\\ContactFormEditForm\\:\\:save\\(\\) should return int but return statement is missing\\.$#"
 			count: 1
@@ -957,7 +1131,7 @@ parameters:
 
 		-
 			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
-			count: 5
+			count: 3
 			path: modules/content_translation/src/ContentTranslationHandler.php
 
 		-
@@ -1255,6 +1429,11 @@ parameters:
 			count: 1
 			path: modules/image/tests/src/Functional/ImageStyleFlushTest.php
 
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 2
+			path: modules/inline_form_errors/tests/src/Unit/FormErrorHandlerTest.php
+
 		-
 			message: "#^Variable \\$reason in empty\\(\\) always exists and is not falsy\\.$#"
 			count: 1
@@ -1465,6 +1644,15 @@ parameters:
 			count: 1
 			path: modules/migrate/src/MigrateException.php
 
+		-
+			message: """
+				#^Call to deprecated method registerLoader\\(\\) of class Doctrine\\\\Common\\\\Annotations\\\\AnnotationRegistry\\:
+				This method is deprecated and will be removed in
+				            doctrine/annotations 2\\.0\\. Annotations will be autoloaded in 2\\.0\\.$#
+			"""
+			count: 1
+			path: modules/migrate/src/Plugin/Discovery/AnnotatedClassDiscoveryAutomatedProviders.php
+
 		-
 			message: "#^Constructor of class Drupal\\\\migrate\\\\Plugin\\\\MigrationPluginManager has an unused parameter \\$language_manager\\.$#"
 			count: 1
@@ -1500,6 +1688,11 @@ parameters:
 			count: 1
 			path: modules/migrate/tests/src/Kernel/MigrateTestBase.php
 
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 2
+			path: modules/migrate/tests/src/Unit/MigrateSqlIdMapEnsureTablesTest.php
+
 		-
 			message: "#^Variable \\$sub_process_plugins might not be defined\\.$#"
 			count: 2
@@ -1605,6 +1798,15 @@ parameters:
 			count: 8
 			path: modules/migrate_drupal_ui/tests/src/Functional/MultilingualReviewPageTestBase.php
 
+		-
+			message: """
+				#^Access to deprecated property \\$needsCleanup of class Drupal\\\\mysql\\\\Driver\\\\Database\\\\mysql\\\\Connection\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. There's no
+				   replacement\\.$#
+			"""
+			count: 1
+			path: modules/mysql/src/Driver/Database/mysql/Connection.php
+
 		-
 			message: "#^Variable \\$last_insert_id might not be defined\\.$#"
 			count: 1
@@ -1625,11 +1827,6 @@ parameters:
 			count: 1
 			path: modules/node/node.module
 
-		-
-			message: "#^Variable \\$node in empty\\(\\) always exists and is not falsy\\.$#"
-			count: 1
-			path: modules/node/node.module
-
 		-
 			message: "#^Method Drupal\\\\node\\\\ConfigTranslation\\\\NodeTypeMapper\\:\\:setEntity\\(\\) should return bool but return statement is missing\\.$#"
 			count: 1
@@ -1755,6 +1952,15 @@ parameters:
 			count: 1
 			path: modules/path_alias/src/AliasManager.php
 
+		-
+			message: """
+				#^Call to deprecated method makeSequenceName\\(\\) of class Drupal\\\\Core\\\\Database\\\\Connection\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is
+				  no replacement\\.$#
+			"""
+			count: 1
+			path: modules/pgsql/src/Driver/Database/pgsql/Schema.php
+
 		-
 			message: "#^Variable \\$table_field might not be defined\\.$#"
 			count: 1
@@ -1765,6 +1971,20 @@ parameters:
 			count: 1
 			path: modules/pgsql/src/Driver/Database/pgsql/Upsert.php
 
+		-
+			message: """
+				#^Call to deprecated method makeSequenceName\\(\\) of class Drupal\\\\Core\\\\Database\\\\Connection\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. There is
+				  no replacement\\.$#
+			"""
+			count: 1
+			path: modules/pgsql/src/Update10101.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: modules/pgsql/tests/src/Unit/SchemaTest.php
+
 		-
 			message: "#^Variable \\$responsive_image_styles in empty\\(\\) always exists and is not falsy\\.$#"
 			count: 1
@@ -1830,6 +2050,11 @@ parameters:
 			count: 1
 			path: modules/serialization/src/Normalizer/EntityNormalizer.php
 
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 3
+			path: modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php
+
 		-
 			message: "#^Method Drupal\\\\shortcut\\\\Form\\\\SetCustomize\\:\\:save\\(\\) should return int but return statement is missing\\.$#"
 			count: 1
@@ -1920,6 +2145,15 @@ parameters:
 			count: 1
 			path: modules/system/tests/modules/batch_test/batch_test.callbacks.inc
 
+		-
+			message: """
+				#^Call to deprecated function deprecation_test_function\\(\\)\\:
+				in drupal\\:8\\.4\\.0 and is removed from drupal\\:9\\.0\\.0\\. This is
+				  the deprecation message for deprecated_test_function\\(\\)\\.$#
+			"""
+			count: 1
+			path: modules/system/tests/modules/deprecation_test/src/DeprecatedController.php
+
 		-
 			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
 			count: 1
@@ -1960,6 +2194,11 @@ parameters:
 			count: 2
 			path: modules/system/tests/src/Functional/Theme/ThemeUiTest.php
 
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 2
+			path: modules/system/tests/src/Kernel/System/CronQueueTest.php
+
 		-
 			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
 			count: 1
@@ -2037,7 +2276,7 @@ parameters:
 
 		-
 			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
-			count: 2
+			count: 1
 			path: modules/user/src/Controller/UserController.php
 
 		-
@@ -2130,6 +2369,22 @@ parameters:
 			count: 1
 			path: modules/user/tests/src/Functional/Views/UserChangedTest.php
 
+		-
+			message: """
+				#^Call to deprecated method expectWarning\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: modules/user/tests/src/Kernel/Views/HandlerFilterRolesTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectWarningMessage\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: modules/user/tests/src/Kernel/Views/HandlerFilterRolesTest.php
+
 		-
 			message: "#^Variable \\$result in isset\\(\\) always exists and is not nullable\\.$#"
 			count: 1
@@ -2485,6 +2740,11 @@ parameters:
 			count: 2
 			path: modules/views/tests/src/Kernel/RenderCacheIntegrationTest.php
 
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 10
+			path: modules/views/tests/src/Unit/ViewsDataTest.php
+
 		-
 			message: "#^Variable \\$relationship_handler in empty\\(\\) always exists and is not falsy\\.$#"
 			count: 1
@@ -2611,9 +2871,40 @@ parameters:
 			path: tests/Drupal/BuildTests/Composer/Template/ComposerProjectTemplatesTest.php
 
 		-
-			message: "#^Variable \\$machine_name_1_value in empty\\(\\) always exists and is not falsy\\.$#"
+			message: """
+				#^Usage of deprecated trait Drupal\\\\BuildTests\\\\Framework\\\\ExternalCommandRequirementsTrait in class Drupal\\\\BuildTests\\\\Framework\\\\Tests\\\\ClassRequiresAvailable\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
+				Drupal\\\\\\\\TestTools\\\\\\\\Extension\\\\\\\\RequiresComposerTrait instead\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/BuildTests/Framework/Tests/ExternalCommandRequirementTest.php
+
+		-
+			message: """
+				#^Usage of deprecated trait Drupal\\\\BuildTests\\\\Framework\\\\ExternalCommandRequirementsTrait in class Drupal\\\\BuildTests\\\\Framework\\\\Tests\\\\ClassRequiresUnavailable\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
+				Drupal\\\\\\\\TestTools\\\\\\\\Extension\\\\\\\\RequiresComposerTrait instead\\.$#
+			"""
 			count: 1
-			path: tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
+			path: tests/Drupal/BuildTests/Framework/Tests/ExternalCommandRequirementTest.php
+
+		-
+			message: """
+				#^Usage of deprecated trait Drupal\\\\BuildTests\\\\Framework\\\\ExternalCommandRequirementsTrait in class Drupal\\\\BuildTests\\\\Framework\\\\Tests\\\\MethodRequires\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
+				Drupal\\\\\\\\TestTools\\\\\\\\Extension\\\\\\\\RequiresComposerTrait instead\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/BuildTests/Framework/Tests/ExternalCommandRequirementTest.php
+
+		-
+			message: """
+				#^Usage of deprecated trait Drupal\\\\BuildTests\\\\Framework\\\\ExternalCommandRequirementsTrait in class Drupal\\\\BuildTests\\\\Framework\\\\Tests\\\\UsesCommandRequirements\\:
+				in drupal\\:10\\.2\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
+				Drupal\\\\\\\\TestTools\\\\\\\\Extension\\\\\\\\RequiresComposerTrait instead\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/BuildTests/Framework/Tests/ExternalCommandRequirementTest.php
 
 		-
 			message: "#^Variable \\$rawResult on left side of \\?\\? always exists and is not nullable\\.$#"
@@ -2630,11 +2921,51 @@ parameters:
 			count: 12
 			path: tests/Drupal/KernelTests/Core/Cache/GenericCacheBackendUnitTestBase.php
 
+		-
+			message: """
+				#^Call to deprecated method expectWarning\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/KernelTests/Core/Config/ConfigInstallTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectWarningMessage\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/KernelTests/Core/Config/ConfigInstallTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectError\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 2
+			path: tests/Drupal/KernelTests/Core/Database/ConnectionTest.php
+
 		-
 			message: "#^Variable \\$expected_driver might not be defined\\.$#"
 			count: 2
 			path: tests/Drupal/KernelTests/Core/Database/DriverSpecificKernelTestBase.php
 
+		-
+			message: """
+				#^Call to deprecated method expectError\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 3
+			path: tests/Drupal/KernelTests/Core/Database/StatementTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectErrorMessage\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 3
+			path: tests/Drupal/KernelTests/Core/Database/StatementTest.php
+
 		-
 			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
 			count: 4
@@ -2695,6 +3026,14 @@ parameters:
 			count: 1
 			path: tests/Drupal/KernelTests/Core/KeyValueStore/GarbageCollectionTest.php
 
+		-
+			message: """
+				#^Call to deprecated method expectErrorMessage\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/KernelTests/Core/Render/Element/MachineNameTest.php
+
 		-
 			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
 			count: 1
@@ -2730,11 +3069,44 @@ parameters:
 			count: 1
 			path: tests/Drupal/Tests/BrowserTestBase.php
 
+		-
+			message: """
+				#^Call to deprecated method getConfig\\(\\) of class GuzzleHttp\\\\Client\\:
+				Client\\:\\:getConfig will be removed in guzzlehttp/guzzle\\:8\\.0\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/BrowserTestBase.php
+
+		-
+			message: """
+				#^Call to deprecated method registerAutoloadNamespace\\(\\) of class Doctrine\\\\Common\\\\Annotations\\\\AnnotationRegistry\\:
+				This method is deprecated and will be removed in
+				            doctrine/annotations 2\\.0\\. Annotations will be autoloaded in 2\\.0\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Component/Annotation/Doctrine/DocParserTest.php
+
 		-
 			message: "#^Constructor of class Symfony\\\\Component\\\\ExpressionLanguage\\\\Expression has an unused parameter \\$expression\\.$#"
 			count: 1
 			path: tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php
 
+		-
+			message: """
+				#^Call to deprecated method expectWarning\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectWarningMessage\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php
+
 		-
 			message: "#^Constructor of class Drupal\\\\Tests\\\\Component\\\\Plugin\\\\Factory\\\\ArgumentsAllNull has an unused parameter \\$charismatic\\.$#"
 			count: 1
@@ -2795,6 +3167,55 @@ parameters:
 			count: 1
 			path: tests/Drupal/Tests/Composer/ComposerTest.php
 
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Cache/CacheCollectorTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectError\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Config/ConfigTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 3
+			path: tests/Drupal/Tests/Core/Cron/CronSuspendQueueDelayTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectError\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Database/ConditionTest.php
+
+		-
+			message: """
+				#^Call to deprecated method findCaller\\(\\) of class Drupal\\\\Core\\\\Database\\\\Log\\:
+				in drupal\\:10\\.1\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
+				  Connection\\:\\:findCallerFromDebugBacktrace\\(\\)\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Database/Stub/StubConnection.php
+
+		-
+			message: """
+				#^Fetching class constant class of deprecated class Drupal\\\\Core\\\\Database\\\\StatementWrapper\\:
+				in drupal\\:10\\.1\\.0 and is removed from drupal\\:11\\.0\\.0\\. Use
+				  \\\\Drupal\\\\Core\\\\Database\\\\StatementWrapperIterator instead\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Database/Stub/StubConnection.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 2
+			path: tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
+
 		-
 			message: "#^Trying to mock an undefined method getRevisionId\\(\\) on class Drupal\\\\Tests\\\\Core\\\\Entity\\\\UrlTestEntity\\.$#"
 			count: 1
@@ -2805,6 +3226,83 @@ parameters:
 			count: 1
 			path: tests/Drupal/Tests/Core/Entity/EntityUrlTest.php
 
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 6
+			path: tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectError\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 2
+			path: tests/Drupal/Tests/Core/EventSubscriber/RedirectResponseSubscriberTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectWarning\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectWarningMessage\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Form/FormCacheTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
+
+		-
+			message: """
+				#^Call to deprecated method getConfig\\(\\) of class GuzzleHttp\\\\Client\\:
+				Client\\:\\:getConfig will be removed in guzzlehttp/guzzle\\:8\\.0\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Http/ClientFactoryTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Menu/ContextualLinkManagerTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 2
+			path: tests/Drupal/Tests/Core/Menu/LocalTaskManagerTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Menu/StaticMenuLinkOverridesTest.php
+
 		-
 			message: "#^Call to method getDefinitions\\(\\) on an unknown class Drupal\\\\Core\\\\Plugin\\\\CategorizingPluginManagerTrait\\.$#"
 			count: 1
@@ -2815,6 +3313,11 @@ parameters:
 			count: 1
 			path: tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php
 
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/Plugin/Discovery/DerivativeDiscoveryDecoratorTest.php
+
 		-
 			message: "#^Constructor of class Drupal\\\\Tests\\\\Core\\\\Plugin\\\\Discovery\\\\TestContainerDerivativeDiscovery has an unused parameter \\$example_service\\.$#"
 			count: 1
@@ -2825,7 +3328,120 @@ parameters:
 			count: 1
 			path: tests/Drupal/Tests/Core/Plugin/TestPluginManager.php
 
+		-
+			message: """
+				#^Call to deprecated method expectError\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Render/ElementTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectErrorMessage\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Render/ElementTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 3
+			path: tests/Drupal/Tests/Core/Routing/RouteBuilderTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectWarning\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Security/DoTrustedCallbackTraitTest.php
+
+		-
+			message: """
+				#^Call to deprecated method expectWarningMessage\\(\\) of class PHPUnit\\\\Framework\\\\TestCase\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/5062$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Security/DoTrustedCallbackTraitTest.php
+
+		-
+			message: """
+				#^Call to deprecated method assertObjectHasAttribute\\(\\) of class PHPUnit\\\\Framework\\\\Assert\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/4601$#
+			"""
+			count: 2
+			path: tests/Drupal/Tests/Core/TempStore/PrivateTempStoreTest.php
+
+		-
+			message: """
+				#^Call to deprecated method assertObjectNotHasAttribute\\(\\) of class PHPUnit\\\\Framework\\\\Assert\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/4601$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/TempStore/PrivateTempStoreTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 2
+			path: tests/Drupal/Tests/Core/TempStore/PrivateTempStoreTest.php
+
+		-
+			message: """
+				#^Call to deprecated method assertObjectHasAttribute\\(\\) of class PHPUnit\\\\Framework\\\\Assert\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/4601$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/TempStore/SharedTempStoreTest.php
+
+		-
+			message: """
+				#^Call to deprecated method assertObjectNotHasAttribute\\(\\) of class PHPUnit\\\\Framework\\\\Assert\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/issues/4601$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/TempStore/SharedTempStoreTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 3
+			path: tests/Drupal/Tests/Core/TempStore/SharedTempStoreTest.php
+
 		-
 			message: "#^Variable \\$value in isset\\(\\) always exists and is not nullable\\.$#"
 			count: 1
 			path: tests/Drupal/Tests/Core/Test/AssertContentTraitTest.php
+
+		-
+			message: """
+				#^Call to deprecated method setMethods\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\MockBuilder\\:
+				https\\://github\\.com/sebastianbergmann/phpunit/pull/3687$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Core/Test/PhpUnitTestRunnerTest.php
+
+		-
+			message: "#^Call to deprecated method withConsecutive\\(\\) of class PHPUnit\\\\Framework\\\\MockObject\\\\Builder\\\\InvocationMocker\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Core/UrlTest.php
+
+		-
+			message: """
+				#^Call to deprecated method getConfig\\(\\) of class GuzzleHttp\\\\ClientInterface\\:
+				ClientInterface\\:\\:getConfig will be removed in guzzlehttp/guzzle\\:8\\.0\\.$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/DrupalTestBrowser.php
+
+		-
+			message: "#^Class Drupal\\\\Tests\\\\Listeners\\\\DrupalListener implements deprecated interface PHPUnit\\\\Framework\\\\TestListener\\.$#"
+			count: 1
+			path: tests/Drupal/Tests/Listeners/DrupalListener.php
+
+		-
+			message: """
+				#^Usage of deprecated trait PHPUnit\\\\Framework\\\\TestListenerDefaultImplementation in class Drupal\\\\Tests\\\\Listeners\\\\DrupalListener\\:
+				The `TestListener` interface is deprecated$#
+			"""
+			count: 1
+			path: tests/Drupal/Tests/Listeners/DrupalListener.php
diff --git a/core/profiles/demo_umami/config/install/core.entity_view_display.node.recipe.full.yml b/core/profiles/demo_umami/config/install/core.entity_view_display.node.recipe.full.yml
index a8dcbb80a2677e0477e17e24726e872f648e5ef5..6eb48804da838599fe67d394ce0b901cc3a71291 100644
--- a/core/profiles/demo_umami/config/install/core.entity_view_display.node.recipe.full.yml
+++ b/core/profiles/demo_umami/config/install/core.entity_view_display.node.recipe.full.yml
@@ -15,12 +15,14 @@ dependencies:
     - field.field.node.recipe.field_tags
     - field.field.node.recipe.layout_builder__layout
     - node.type.recipe
+    - views.view.related_recipes
   module:
     - layout_builder
     - layout_discovery
     - options
     - text
     - user
+    - views
   theme:
     - umami
 third_party_settings:
@@ -245,6 +247,26 @@ third_party_settings:
             weight: 0
             additional: {  }
         third_party_settings: {  }
+      -
+        layout_id: layout_onecol
+        layout_settings:
+          label: related
+          context_mapping: {  }
+        components:
+          3164f99a-0a52-403e-a921-fad17cb6e8c7:
+            uuid: 3164f99a-0a52-403e-a921-fad17cb6e8c7
+            region: content
+            configuration:
+              id: 'views_block:related_recipes-related_recipes_block'
+              label: ''
+              label_display: visible
+              provider: views
+              context_mapping: {  }
+              views_label: ''
+              items_per_page: none
+            weight: 0
+            additional: {  }
+        third_party_settings: {  }
 id: node.recipe.full
 targetEntityType: node
 bundle: recipe
diff --git a/core/profiles/demo_umami/config/install/views.view.related_recipes.yml b/core/profiles/demo_umami/config/install/views.view.related_recipes.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ff8c4efa54088f374d11e806d9b3deafd4b71f2f
--- /dev/null
+++ b/core/profiles/demo_umami/config/install/views.view.related_recipes.yml
@@ -0,0 +1,305 @@
+langcode: en
+status: true
+dependencies:
+  config:
+    - core.entity_view_mode.node.card
+    - node.type.recipe
+  module:
+    - node
+    - user
+id: related_recipes
+label: Related recipes
+module: views
+description: 'Related recipes listing'
+tag: ''
+base_table: node_field_data
+base_field: nid
+display:
+  default:
+    id: default
+    display_title: Default
+    display_plugin: default
+    position: 0
+    display_options:
+      title: 'Related recipes'
+      fields:
+        title:
+          id: title
+          table: node_field_data
+          field: title
+          relationship: none
+          group_type: group
+          admin_label: ''
+          entity_type: node
+          entity_field: title
+          plugin_id: field
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            make_link: false
+            absolute: false
+            word_boundary: false
+            ellipsis: false
+            strip_tags: false
+            trim: false
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: string
+          settings:
+            link_to_entity: true
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+      pager:
+        type: some
+        options:
+          offset: 0
+          items_per_page: 4
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: true
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      access:
+        type: perm
+        options:
+          perm: 'access content'
+      cache:
+        type: tag
+        options: {  }
+      empty: {  }
+      sorts:
+        created:
+          id: created
+          table: node_field_data
+          field: created
+          relationship: none
+          group_type: group
+          admin_label: ''
+          entity_type: node
+          entity_field: created
+          plugin_id: date
+          order: DESC
+          expose:
+            label: ''
+            field_identifier: ''
+          exposed: false
+          granularity: second
+      arguments:
+        nid:
+          id: nid
+          table: node_field_data
+          field: nid
+          relationship: none
+          group_type: group
+          admin_label: ''
+          entity_type: node
+          entity_field: nid
+          plugin_id: node_nid
+          default_action: default
+          exception:
+            value: all
+            title_enable: false
+            title: All
+          title_enable: false
+          title: ''
+          default_argument_type: node
+          default_argument_options: {  }
+          summary_options:
+            base_path: ''
+            count: true
+            override: false
+            items_per_page: 25
+          summary:
+            sort_order: asc
+            number_of_records: 0
+            format: default_summary
+          specify_validation: false
+          validate:
+            type: none
+            fail: 'not found'
+          validate_options: {  }
+          break_phrase: false
+          not: true
+        field_recipe_category_target_id:
+          id: field_recipe_category_target_id
+          table: node__field_recipe_category
+          field: field_recipe_category_target_id
+          relationship: none
+          group_type: group
+          admin_label: ''
+          plugin_id: numeric
+          default_action: default
+          exception:
+            value: all
+            title_enable: false
+            title: All
+          title_enable: false
+          title: ''
+          default_argument_type: taxonomy_tid
+          default_argument_options:
+            term_page: '0'
+            node: true
+            limit: false
+            vids: {  }
+            anyall: ','
+          summary_options:
+            base_path: ''
+            count: true
+            override: false
+            items_per_page: 25
+          summary:
+            sort_order: asc
+            number_of_records: 0
+            format: default_summary
+          specify_validation: false
+          validate:
+            type: none
+            fail: 'not found'
+          validate_options: {  }
+          break_phrase: true
+          not: false
+      filters:
+        status:
+          id: status
+          table: node_field_data
+          field: status
+          entity_type: node
+          entity_field: status
+          plugin_id: boolean
+          value: '1'
+          group: 1
+          expose:
+            operator: ''
+            operator_limit_selection: false
+            operator_list: {  }
+        type:
+          id: type
+          table: node_field_data
+          field: type
+          entity_type: node
+          entity_field: type
+          plugin_id: bundle
+          value:
+            recipe: recipe
+          expose:
+            operator_limit_selection: false
+            operator_list: {  }
+        langcode:
+          id: langcode
+          table: node_field_data
+          field: langcode
+          relationship: none
+          group_type: group
+          admin_label: ''
+          entity_type: node
+          entity_field: langcode
+          plugin_id: language
+          operator: in
+          value:
+            '***LANGUAGE_language_content***': '***LANGUAGE_language_content***'
+          group: 1
+          exposed: false
+          expose:
+            operator_id: ''
+            label: ''
+            description: ''
+            use_operator: false
+            operator: ''
+            operator_limit_selection: false
+            operator_list: {  }
+            identifier: ''
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+            reduce: false
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+      style:
+        type: default
+        options:
+          row_class: ''
+          default_row_class: true
+          uses_fields: false
+      row:
+        type: 'entity:node'
+        options:
+          relationship: none
+          view_mode: card
+      query:
+        type: views_query
+        options:
+          query_comment: ''
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_tags: {  }
+      relationships: {  }
+      css_class: grid--4
+      header: {  }
+      footer: {  }
+      display_extenders: {  }
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - 'user.node_grants:view'
+        - user.permissions
+      tags: {  }
+  related_recipes_block:
+    id: related_recipes_block
+    display_title: Block
+    display_plugin: block
+    position: 1
+    display_options:
+      display_extenders: {  }
+      block_description: 'Related recipes'
+      block_hide_empty: true
+    cache_metadata:
+      max-age: -1
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url
+        - 'user.node_grants:view'
+        - user.permissions
+      tags: {  }
diff --git a/core/profiles/demo_umami/tests/src/Functional/DemoUmamiProfileTest.php b/core/profiles/demo_umami/tests/src/Functional/DemoUmamiProfileTest.php
index 6b831095fe0fdc691ff3b31098c1dc1523070388..246b555153f28301ba3d1463651e5f77b73cb15b 100644
--- a/core/profiles/demo_umami/tests/src/Functional/DemoUmamiProfileTest.php
+++ b/core/profiles/demo_umami/tests/src/Functional/DemoUmamiProfileTest.php
@@ -18,6 +18,7 @@
  * Tests demo_umami profile.
  *
  * @group demo_umami
+ * @group #slow
  */
 class DemoUmamiProfileTest extends BrowserTestBase {
   use AssertConfigTrait;
@@ -37,10 +38,23 @@ protected function installParameters() {
    */
   protected $profile = 'demo_umami';
 
+  /**
+   * Tests some features specific to being a demonstration profile.
+   */
+  public function testDemoSpecificFeatures() {
+    // This test coverage is organized into separate protected methods rather
+    // than individual test methods to avoid having to reinstall Umami for
+    // a handful of assertions each.
+    $this->testUser();
+    $this->testWarningsOnStatusPage();
+    $this->testAppearance();
+    $this->testDemonstrationWarningMessage();
+  }
+
   /**
    * Tests demo_umami profile warnings shown on Status Page.
    */
-  public function testWarningsOnStatusPage() {
+  protected function testWarningsOnStatusPage() {
     $account = $this->drupalCreateUser(['administer site configuration']);
     $this->drupalLogin($account);
 
@@ -128,7 +142,7 @@ protected function assertDefaultConfig(StorageInterface $default_config_storage,
   /**
    * Tests that the users can log in with the admin password entered at install.
    */
-  public function testUser() {
+  protected function testUser() {
     $password = $this->rootUser->pass_raw;
     $ids = \Drupal::entityQuery('user')
       ->accessCheck(FALSE)
@@ -175,7 +189,7 @@ public function testEditNodesByAdmin() {
   /**
    * Tests that the Umami theme is available on the Appearance page.
    */
-  public function testAppearance() {
+  protected function testAppearance() {
     $account = $this->drupalCreateUser(['administer themes']);
     $this->drupalLogin($account);
     $webassert = $this->assertSession();
@@ -187,7 +201,7 @@ public function testAppearance() {
   /**
    * Tests that the toolbar warning only appears on the admin pages.
    */
-  public function testDemonstrationWarningMessage() {
+  protected function testDemonstrationWarningMessage() {
     $permissions = [
       'access content overview',
       'access toolbar',
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6a16046f01232d3b243ca05500c2733b3a5f924e
--- /dev/null
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryFrontPagePerformanceTest.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\Tests\demo_umami\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\PerformanceTestBase;
+
+/**
+ * Tests demo_umami profile performance.
+ *
+ * @group OpenTelemetry
+ * @group #slow
+ */
+class OpenTelemetryFrontPagePerformanceTest extends PerformanceTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $profile = 'demo_umami';
+
+  /**
+   * Logs front page tracing data with a cold cache.
+   */
+  public function testFrontPageColdCache() {
+    // @todo: Chromedriver doesn't collect tracing performance logs for the very
+    // first request in a test, so warm it up.
+    // See https://www.drupal.org/project/drupal/issues/3379750
+    $this->drupalGet('user/login');
+    $this->rebuildAll();
+    $this->collectPerformanceData(function () {
+      $this->drupalGet('<front>');
+    }, 'umamiFrontPageColdCache');
+    $this->assertSession()->pageTextContains('Umami');
+  }
+
+  /**
+   * Logs front page tracing data with a hot cache.
+   *
+   * Hot here means that all possible caches are warmed.
+   */
+  public function testFrontPageHotCache() {
+    // Request the page twice so that asset aggregates and image derivatives are
+    // definitely cached in the browser cache. The first response builds the
+    // file and serves from PHP with private, no-store headers. The second
+    // request will get the file served directly from disk by the browser with
+    // cacheable headers, so only the third request actually has the files
+    // in the browser cache.
+    $this->drupalGet('<front>');
+    $this->drupalGet('<front>');
+    $this->collectPerformanceData(function () {
+      $this->drupalGet('<front>');
+    }, 'umamiFrontPageWarmCache');
+  }
+
+  /**
+   * Logs front page tracing data with a lukewarm cache.
+   *
+   * Cool here means that 'global' site caches are warm but anything
+   * specific to the front page is cold.
+   */
+  public function testFrontPageCoolCache() {
+    // First of all visit the front page to ensure the image style exists.
+    $this->drupalGet('<front>');
+    $this->rebuildAll();
+    // Now visit a different page to warm non-route-specific caches.
+    $this->drupalGet('/user/login');
+    $this->collectPerformanceData(function () {
+      $this->drupalGet('<front>');
+    }, 'umamiFrontPageCoolCache');
+  }
+
+}
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d8b22001cf9616536321bc2c7f348d982f338247
--- /dev/null
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/OpenTelemetryNodePagePerformanceTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\Tests\demo_umami\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\PerformanceTestBase;
+
+/**
+ * Tests demo_umami profile performance.
+ *
+ * @group OpenTelemetry
+ * @group #slow
+ */
+class OpenTelemetryNodePagePerformanceTest extends PerformanceTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $profile = 'demo_umami';
+
+  /**
+   * Logs node page tracing data with a cold cache.
+   */
+  public function testNodePageColdCache() {
+    // @todo: Chromedriver doesn't collect tracing performance logs for the very
+    // first request in a test, so warm it up.
+    // See https://www.drupal.org/project/drupal/issues/3379750
+    $this->drupalGet('user/login');
+    $this->rebuildAll();
+    $this->collectPerformanceData(function () {
+      $this->drupalGet('/node/1');
+    }, 'umamiNodePageColdCache');
+    $this->assertSession()->pageTextContains('quiche');
+  }
+
+  /**
+   * Logs node page tracing data with a hot cache.
+   *
+   * Hot here means that all possible caches are warmed.
+   */
+  public function testNodePageHotCache() {
+    // Request the page twice so that asset aggregates are definitely cached in
+    // the browser cache.
+    $this->drupalGet('node/1');
+    $this->drupalGet('node/1');
+    $this->collectPerformanceData(function () {
+      $this->drupalGet('/node/1');
+    }, 'umamiNodePageHotCache');
+    $this->assertSession()->pageTextContains('quiche');
+  }
+
+  /**
+   * Logs node/1 tracing data with a cool cache.
+   *
+   * Cool here means that 'global' site caches are warm but anything
+   * specific to the route or path is cold.
+   */
+  public function testNodePageCoolCache() {
+    // First of all visit the node page to ensure the image style exists.
+    $this->drupalGet('node/1');
+    $this->rebuildAll();
+    // Now visit a non-node page to warm non-route-specific caches.
+    $this->drupalGet('/user/login');
+    $this->collectPerformanceData(function () {
+      $this->drupalGet('/node/1');
+    }, 'umamiNodePageCoolCache');
+    $this->assertSession()->pageTextContains('quiche');
+  }
+
+  /**
+   * Log node/1 tracing data with a warm cache.
+   *
+   * Warm here means that 'global' site caches and route-specific caches are
+   * warm but caches specific to this particular node/path are not.
+   */
+  public function testNodePageWarmCache() {
+    // First of all visit the node page to ensure the image style exists.
+    $this->drupalGet('node/1');
+    $this->rebuildAll();
+    // Now visit a different node page to warm non-path-specific caches.
+    $this->drupalGet('/node/2');
+    $this->collectPerformanceData(function () {
+      $this->drupalGet('/node/1');
+    }, 'umamiNodePageWarmCache');
+    $this->assertSession()->pageTextContains('quiche');
+  }
+
+}
diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php
index baecf5b8387e9ca066e95c288ea43f3c7542788a..d89ee8caad957491b92db6a9f4b646e1f79d81bb 100644
--- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php
+++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php
@@ -7,7 +7,7 @@
 /**
  * Tests demo_umami profile performance.
  *
- * @group performance
+ * @group Performance
  */
 class PerformanceTest extends PerformanceTestBase {
 
@@ -19,11 +19,19 @@ class PerformanceTest extends PerformanceTestBase {
   /**
    * Just load the front page.
    */
-  public function testFrontPage(): void {
-    $this->drupalGet('<front>');
+  public function testPagesAnonymous(): void {
+    $performance_data = $this->collectPerformanceData(function () {
+      $this->drupalGet('<front>');
+    });
     $this->assertSession()->pageTextContains('Umami');
-    $this->assertSame(2, $this->stylesheetCount);
-    $this->assertSame(1, $this->scriptCount);
+    $this->assertSame(2, $performance_data->getStylesheetCount());
+    $this->assertSame(1, $performance_data->getScriptCount());
+
+    $performance_data = $this->collectPerformanceData(function () {
+      $this->drupalGet('node/1');
+    });
+    $this->assertSame(2, $performance_data->getStylesheetCount());
+    $this->assertSame(1, $performance_data->getScriptCount());
   }
 
   /**
@@ -32,10 +40,12 @@ public function testFrontPage(): void {
   public function testFrontPagePerformance(): void {
     $admin_user = $this->drupalCreateUser(['access toolbar']);
     $this->drupalLogin($admin_user);
-    $this->drupalGet('<front>');
+    $performance_data = $this->collectPerformanceData(function () {
+      $this->drupalGet('<front>');
+    });
     $this->assertSession()->pageTextContains('Umami');
-    $this->assertSame(2, $this->stylesheetCount);
-    $this->assertSame(1, $this->scriptCount);
+    $this->assertSame(2, $performance_data->getStylesheetCount());
+    $this->assertSame(2, $performance_data->getScriptCount());
   }
 
 }
diff --git a/core/profiles/demo_umami/themes/umami/components/branding/branding.css b/core/profiles/demo_umami/themes/umami/components/branding/branding.css
index e5ea8eaf102fe6cc69dace435dc0198859fdc1b9..1cda3953070509da6c183cd08b0f3c9d9bbc3fff 100644
--- a/core/profiles/demo_umami/themes/umami/components/branding/branding.css
+++ b/core/profiles/demo_umami/themes/umami/components/branding/branding.css
@@ -3,14 +3,8 @@
  * This file is used to style the branding block.
  */
 
-.branding {
-  flex: 0 1 40%;
-}
-
 @media screen and (min-width: 48em) {
   .branding {
-    flex: 0 1 220px;
-    margin: 2.5rem 0;
     text-align: left;
   }
 }
diff --git a/core/profiles/demo_umami/themes/umami/css/components/content-types/recipe/recipe.css b/core/profiles/demo_umami/themes/umami/css/components/content-types/recipe/recipe.css
index dc40728b37369bbef749c7bf85690ad28818ca90..fb360d3fa6d800b9c7e45054803377dfe279c093 100644
--- a/core/profiles/demo_umami/themes/umami/css/components/content-types/recipe/recipe.css
+++ b/core/profiles/demo_umami/themes/umami/css/components/content-types/recipe/recipe.css
@@ -72,3 +72,7 @@
 .node--type-recipe.node--view-mode-full .field--name-field-difficulty {
   background-image: url(../../../../images/svg/difficulty.svg);
 }
+
+.node--type-recipe.node--view-mode-full .block-views-blockrelated-recipes-related-recipes-block {
+  margin-top: 2rem;
+}
diff --git a/core/profiles/demo_umami/themes/umami/css/components/regions/header/header.css b/core/profiles/demo_umami/themes/umami/css/components/regions/header/header.css
index f9b13115473c75abd434f785b86e7a429fed76ac..1a8e756e0808946d67e39b5d37cde91aecd1b95b 100644
--- a/core/profiles/demo_umami/themes/umami/css/components/regions/header/header.css
+++ b/core/profiles/demo_umami/themes/umami/css/components/regions/header/header.css
@@ -36,3 +36,14 @@
     padding: 0;
   }
 }
+
+.region-header .block-system-branding-block {
+  flex: 0 1 40%;
+}
+
+@media screen and (min-width: 48em) {
+  .region-header .block-system-branding-block {
+    flex: 0 1 220px;
+    margin: 2.5rem 0;
+  }
+}
diff --git a/core/profiles/demo_umami/themes/umami/umami.info.yml b/core/profiles/demo_umami/themes/umami/umami.info.yml
index 441ebdaf763cc85b7f33f700670bac476d932ad7..451d07d3a38260ec905b07f1382e6ead257f2d97 100644
--- a/core/profiles/demo_umami/themes/umami/umami.info.yml
+++ b/core/profiles/demo_umami/themes/umami/umami.info.yml
@@ -7,6 +7,8 @@ libraries:
   - umami/classy.base
   - core/normalize
   - umami/global
+dependencies:
+  - drupal:sdc
 
 libraries-override:
   layout_builder/twocol_section:
diff --git a/core/profiles/demo_umami/themes/umami/umami.theme b/core/profiles/demo_umami/themes/umami/umami.theme
index 78b19feaa9f9c0a500f63b2bea41e42286fafe6a..3775471a5923d806132dc1c430056c31dc87c163 100644
--- a/core/profiles/demo_umami/themes/umami/umami.theme
+++ b/core/profiles/demo_umami/themes/umami/umami.theme
@@ -7,6 +7,7 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
 use Drupal\search\SearchPageInterface;
 use Drupal\views\Form\ViewsForm;
 use Drupal\Core\Render\Element;
@@ -150,7 +151,7 @@ function umami_form_alter(array &$form, FormStateInterface $form_state, $form_id
 function umami_preprocess_image_widget(&$variables) {
   if (!empty($variables['element']['fids']['#value'])) {
     $file = reset($variables['element']['#files']);
-    $variables['data']["file_{$file->id()}"]['filename']['#suffix'] = ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
+    $variables['data']["file_{$file->id()}"]['filename']['#suffix'] = ' <span class="file-size">(' . ByteSizeMarkup::create($file->getSize()) . ')</span> ';
   }
 }
 
diff --git a/core/profiles/standard/tests/src/Functional/StandardTest.php b/core/profiles/standard/tests/src/Functional/StandardTest.php
index 40a04624ac4a2c9ff21fdc6089a1c18e1f5db13c..15ed8479bbe9b24a1850362a54635068a58b16fc 100644
--- a/core/profiles/standard/tests/src/Functional/StandardTest.php
+++ b/core/profiles/standard/tests/src/Functional/StandardTest.php
@@ -85,7 +85,7 @@ public function testStandard() {
     $this->drupalLogin($this->adminUser);
     $this->drupalGet('node/1');
     // Verify that a line break is present.
-    $this->assertSession()->responseContains('Then she picked out two somebodies,<br />Sally and me');
+    $this->assertSession()->responseContains('Then she picked out two somebodies,<br>Sally and me');
     $this->submitForm([
       'subject[0][value]' => 'Barfoo',
       'comment_body[0][value]' => 'Then she picked out two somebodies, Sally and me',
diff --git a/core/scripts/dev/commit-code-check.sh b/core/scripts/dev/commit-code-check.sh
index a22a7808afb9fbe1749ad11c6af1a8e25c27dfe3..2d47e0f950c5ddb43ec5989980907e26a4fc9c86 100755
--- a/core/scripts/dev/commit-code-check.sh
+++ b/core/scripts/dev/commit-code-check.sh
@@ -176,7 +176,7 @@
     CKEDITOR5_PLUGINS_CHANGED=1;
   fi;
 
-  if [[ $FILE == "core/misc/cspell/dictionary.txt" ]]; then
+  if [[ $FILE == "core/misc/cspell/dictionary.txt" || $FILE == "core/.cspell.json" ]]; then
     CSPELL_DICTIONARY_FILE_CHANGED=1;
   fi
 done
@@ -215,13 +215,15 @@
   exit 1;
 fi
 
-# Run spellcheck:core when cspell files are changed.
 # Check all files for spelling in one go for better performance.
 if [[ $CSPELL_DICTIONARY_FILE_CHANGED == "1" ]] ; then
   printf "\nRunning spellcheck on *all* files.\n"
-  yarn run -s spellcheck:core --no-must-find-files --root $TOP_LEVEL $ABS_FILES
+  yarn run -s spellcheck:core --no-must-find-files --no-progress
 else
-  yarn run -s spellcheck --no-must-find-files --root $TOP_LEVEL $ABS_FILES
+  # Check all files for spelling in one go for better performance. We pipe the
+  # list files in so we obey the globs set on the spellcheck:core command in
+  # core/package.json.
+  echo "${ABS_FILES}" | tr ' ' '\n' | yarn run -s spellcheck:core --no-must-find-files --file-list stdin
 fi
 
 if [ "$?" -ne "0" ]; then
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index 7cf7a856c6f1925b679438e4bd16f52306a14c24..cc5b830f4104e6c89dbac3323db0174a1471259e 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -404,6 +404,8 @@ function simpletest_script_parse_args() {
     'execute-test' => '',
     'xml' => '',
     'non-html' => FALSE,
+    'ci-parallel-node-index' => 1,
+    'ci-parallel-node-total' => 1,
   ];
 
   // Override with set values.
@@ -896,6 +898,7 @@ function simpletest_script_get_test_list() {
   );
   $types_processed = empty($args['types']);
   $test_list = [];
+  $slow_tests = [];
   if ($args['all'] || $args['module']) {
     try {
       $groups = $test_discovery->getTestClasses($args['module'], $args['types']);
@@ -905,11 +908,17 @@ function simpletest_script_get_test_list() {
       echo (string) $e;
       exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
     }
+    if ((int) $args['ci-parallel-node-total'] > 1) {
+      if (key($groups) === '#slow') {
+        $slow_tests = array_keys(array_shift($groups));
+      }
+    }
     $all_tests = [];
     foreach ($groups as $group => $tests) {
       $all_tests = array_merge($all_tests, array_keys($tests));
     }
     $test_list = array_unique($all_tests);
+    $test_list = array_diff($test_list, $slow_tests);
   }
   else {
     if ($args['class']) {
@@ -1024,6 +1033,13 @@ function simpletest_script_get_test_list() {
     simpletest_script_print_error('No valid tests were specified.');
     exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
   }
+
+  if ((int) $args['ci-parallel-node-total'] > 1) {
+    $slow_tests_per_job = ceil(count($slow_tests) / $args['ci-parallel-node-total']);
+    $tests_per_job = ceil(count($test_list) / $args['ci-parallel-node-total']);
+    $test_list = array_merge(array_slice($slow_tests, ($args['ci-parallel-node-index'] -1) * $slow_tests_per_job, $slow_tests_per_job), array_slice($test_list, ($args['ci-parallel-node-index'] - 1) * $tests_per_job, $tests_per_job));
+  }
+
   return $test_list;
 }
 
diff --git a/core/scripts/update-countries.sh b/core/scripts/update-countries.sh
index 043eceae800d2c1ae7278391736ee055dbee54a0..9aa70dc9db2864966a30f358adcc00cc03c22037 100755
--- a/core/scripts/update-countries.sh
+++ b/core/scripts/update-countries.sh
@@ -34,16 +34,6 @@
   exit('CLDR data file not found. (' . $uri . ")\n\n" . $usage . "\n");
 }
 
-// Fake the t() function used in CountryManager.php instead of attempting a full
-// Drupal bootstrap of core/includes/bootstrap.inc (where t() is declared).
-if (!function_exists('t')) {
-
-  function t($string) {
-    return $string;
-  }
-
-}
-
 // Read in existing codes.
 // @todo Allow to remove previously existing country codes.
 // @see https://www.drupal.org/node/1436754
diff --git a/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php
index 7059c90b529be052842b941a3762ecf2e9cd0bcc..fe72a958a6b25c310d379e6ce592ee5a9fefe375 100644
--- a/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php
+++ b/core/tests/Drupal/BuildTests/Composer/Component/ComponentsIsolatedBuildTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\BuildTests\Composer\ComposerBuildTestBase;
 use Drupal\Composer\Composer;
+use Symfony\Component\Finder\Finder;
 
 /**
  * Try to install dependencies per component, using Composer.
@@ -32,7 +33,7 @@ public function provideComponentPaths(): array {
 
     /** @var \Symfony\Component\Finder\SplFileInfo $path */
     foreach ($composer_json_finder->getIterator() as $path) {
-      $data[] = ['/' . $path->getRelativePath()];
+      $data[$path->getRelativePath()] = ['/' . $path->getRelativePath()];
     }
     return $data;
   }
@@ -45,8 +46,12 @@ public function provideComponentPaths(): array {
   public function testComponentComposerJson(string $component_path): void {
     // Only copy the components. Copy all of them because some of them depend on
     // each other.
-    $finder = $this->getCodebaseFinder();
-    $finder->in($this->getDrupalRoot() . static::$componentsPath);
+    $finder = new Finder();
+    $finder->files()
+      ->ignoreUnreadableDirs()
+      ->in($this->getDrupalRoot() . static::$componentsPath)
+      ->ignoreDotFiles(FALSE)
+      ->ignoreVCS(FALSE);
     $this->copyCodebase($finder->getIterator());
 
     $working_dir = $this->getWorkingPath() . static::$componentsPath . $component_path;
diff --git a/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
index 7d10abb86367ac957bc2b3a2c22de38e6248b53b..70ee4569755df8d169b1b9c0c4f7711321a9e881 100644
--- a/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
+++ b/core/tests/Drupal/BuildTests/Framework/BuildTestBase.php
@@ -45,8 +45,6 @@
  *   built into the test, or abstract base classes.
  * - Allow parallel testing, using random/unique port numbers for different HTTP
  *   servers.
- * - Allow the use of PHPUnit-style (at)require annotations for external shell
- *   commands.
  *
  * We don't use UiHelperInterface because it is too tightly integrated to
  * Drupal.
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php
index eb66398b572a193cc8bcbf39ac0b95c94681486f..77e68d21ec0b99c9f1a28d3f240262bb379337d3 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/ThrobberTest.php
@@ -28,18 +28,6 @@ class ThrobberTest extends WebDriverTestBase {
    */
   protected $defaultTheme = 'stark';
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    $admin_user = $this->drupalCreateUser([
-      'administer views',
-    ]);
-    $this->drupalLogin($admin_user);
-  }
-
   /**
    * Tests theming throbber element.
    */
@@ -47,6 +35,11 @@ public function testThemingThrobberElement() {
     $session = $this->getSession();
     $web_assert = $this->assertSession();
     $page = $session->getPage();
+    $admin_user = $this->drupalCreateUser([
+      'administer views',
+      'administer blocks',
+    ]);
+    $this->drupalLogin($admin_user);
 
     $custom_ajax_progress_indicator_fullscreen = <<<JS
       Drupal.theme.ajaxProgressIndicatorFullscreen = function () {
@@ -92,23 +85,17 @@ public function testThemingThrobberElement() {
     $this->assertNotNull($web_assert->waitForElement('css', '.custom-ajax-progress-throbber'), 'Custom ajaxProgressThrobber.');
     hold_test_response(FALSE);
     $web_assert->assertNoElementAfterWait('css', '.custom-ajax-progress-throbber');
-  }
-
-  /**
-   * Tests progress throbber element position.
-   */
-  public function testProgressThrobberPosition() {
-    $this->drupalLogin($this->rootUser);
 
+    // Test progress throbber position on a dropbutton in a table display.
     $this->drupalGet('/admin/structure/block');
     $this->clickLink('Place block');
-    hold_test_response(FALSE);
-    $this->assertSession()->waitForText('Place Block');
-    $this->clickLink('Place block');
+    $web_assert->assertWaitOnAjaxRequest();
+    $this->assertNotEmpty($web_assert->waitForElementVisible('css', '#drupal-modal'));
     hold_test_response(TRUE);
-    $this->assertSession()->elementExists('xpath', '//div[contains(@class, "dropbutton-wrapper")]/following-sibling::div[contains(@class, "ajax-progress-throbber")]');
+    $this->clickLink('Place block');
+    $this->assertNotNull($web_assert->waitForElement('xpath', '//div[contains(@class, "dropbutton-wrapper")]/following-sibling::div[contains(@class, "ajax-progress-throbber")]'));
     hold_test_response(FALSE);
-    $this->assertSession()->assertNoElementAfterWait('css', '.ajax-progress-throbber');
+    $web_assert->assertNoElementAfterWait('css', '.ajax-progress-throbber');
   }
 
 }
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
index 613269705e1c2f95f3108f086441b9a98b0a451b..899b80c50449742d7fadb4fa6f4b5a634276186f 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/MachineNameTest.php
@@ -104,11 +104,6 @@ public function testMachineName() {
     // Assert that a machine name based on a default value is initialized.
     $this->assertJsCondition('jQuery("#edit-machine-name-3-label-machine-name-suffix .machine-name-value").html() == "yet_another_machine_name"');
 
-    // Field must be present for the rest of the test to work.
-    if (empty($machine_name_1_value)) {
-      $this->fail('Cannot finish test, missing machine name field');
-    }
-
     // Test each value for conversion to a machine name.
     foreach ($test_values as $test_info) {
       // Set the value for the field, triggering the machine name update.
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php b/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php
index 9d247a12b4953472c9814ed5ec463540eff4ece1..b7b2bed33ef6a0d65076c6b8547f55dcfc9cdfd5 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php
@@ -4,8 +4,7 @@
 
 namespace Drupal\FunctionalJavascriptTests;
 
-use Drupal\Core\Url;
-use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\PerformanceTestTrait;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -14,110 +13,28 @@
  * @ingroup testing
  */
 class PerformanceTestBase extends WebDriverTestBase {
-
-  /**
-   * The number of stylesheets requested.
-   */
-  protected int $stylesheetCount = 0;
-
-  /**
-   * The number of scripts requested.
-   */
-  protected int $scriptCount = 0;
+  use PerformanceTestTrait;
 
   /**
    * {@inheritdoc}
    */
   protected function setUp(): void {
     parent::setUp();
-    \Drupal::configFactory()->getEditable('system.performance')
-      ->set('css.preprocess', TRUE)
-      ->set('js.preprocess', TRUE)
-      ->save();
+    $this->doSetUpTasks();
   }
 
   /**
    * {@inheritdoc}
    */
   protected function installModulesFromClassProperty(ContainerInterface $container) {
-    // Bypass everything that WebDriverTestBase does here to get closer to
-    // a production configuration.
-    BrowserTestBase::installModulesFromClassProperty($container);
+    $this->doInstallModulesFromClassProperty($container);
   }
 
   /**
    * {@inheritdoc}
    */
   protected function getMinkDriverArgs() {
-
-    // Add performance logging preferences to the existing driver arguments to
-    // avoid clobbering anything set via environment variables.
-    // @see https://chromedriver.chromium.org/logging/performance-log
-    $parent_driver_args = parent::getMinkDriverArgs();
-    $driver_args = json_decode($parent_driver_args, TRUE);
-
-    $driver_args[1]['goog:loggingPrefs'] = [
-      'browser' => 'ALL',
-      'performance' => 'ALL',
-      'performanceTimeline' => 'ALL',
-    ];
-    $driver_args[1]['chromeOptions']['perfLoggingPrefs'] = [
-      'traceCategories' => 'devtools.timeline',
-      'enableNetwork' => TRUE,
-    ];
-
-    return json_encode($driver_args);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function drupalGet($path, array $options = [], array $headers = []): string {
-    // Reset the performance log from any previous HTTP requests. The log is
-    // cumulative until it is collected explicitly.
-    $session = $this->getSession();
-    $session->getDriver()->getWebDriverSession()->log('performance');
-    $return = parent::drupalGet($path, $options, $headers);
-    $this->getChromeDriverPerformanceMetrics($path);
-    return $return;
-  }
-
-  /**
-   * Gets the chromedriver performance log and extracts metrics from it.
-   */
-  protected function getChromeDriverPerformanceMetrics(string|Url $path): void {
-    $session = $this->getSession();
-    $performance_log = $session->getDriver()->getWebDriverSession()->log('performance');
-
-    $messages = [];
-    foreach ($performance_log as $entry) {
-      $decoded = json_decode($entry['message'], TRUE);
-      $messages[] = $decoded['message'];
-    }
-    $this->collectNetworkData($path, $messages);
-  }
-
-  /**
-   * Prepares data for assertions.
-   *
-   * @param string|\Drupal\Core\Url $path
-   *   The path as passed to static::drupalGet().
-   * @param array $messages
-   *   The chromedriver performance log messages.
-   */
-  protected function collectNetworkData(string|Url $path, array $messages): void {
-    $this->stylesheetCount = 0;
-    $this->scriptCount = 0;
-    foreach ($messages as $message) {
-      if ($message['method'] === 'Network.responseReceived') {
-        if ($message['params']['type'] === 'Stylesheet') {
-          $this->stylesheetCount++;
-        }
-        if ($message['params']['type'] === 'Script') {
-          $this->scriptCount++;
-        }
-      }
-    }
+    return $this->doGetMinkDriverArgs();
   }
 
 }
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php b/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
index e3f011c554f409085334a9ac4a348e05bbc47954..40e35f6aa9b87c090e9fbbcb1cc3453d2fc12806 100644
--- a/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
+++ b/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php
@@ -112,6 +112,7 @@ protected function tearDown(): void {
       $warnings = $this->getSession()->evaluateScript("JSON.parse(sessionStorage.getItem('js_testing_log_test.warnings') || JSON.stringify([]))");
       foreach ($warnings as $warning) {
         if (str_starts_with($warning, '[Deprecation]')) {
+          // phpcs:ignore Drupal.Semantics.FunctionTriggerError
           @trigger_error('Javascript Deprecation:' . substr($warning, 13), E_USER_DEPRECATED);
         }
       }
diff --git a/core/tests/Drupal/FunctionalTests/Bootstrap/UncaughtExceptionTest.php b/core/tests/Drupal/FunctionalTests/Bootstrap/UncaughtExceptionTest.php
index d01851a848c6a5e627f73941c86258117c5cb147..2635280d0bf9b73f0750294c64985db39f747e93 100644
--- a/core/tests/Drupal/FunctionalTests/Bootstrap/UncaughtExceptionTest.php
+++ b/core/tests/Drupal/FunctionalTests/Bootstrap/UncaughtExceptionTest.php
@@ -9,6 +9,7 @@
  * Tests kernel panic when things are really messed up.
  *
  * @group system
+ * @group #slow
  */
 class UncaughtExceptionTest extends BrowserTestBase {
 
diff --git a/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php
index 82e98d91a8992659339c416e2746502d9ca557f3..e9cd2e70adb6ec1cc7cc239802e2c4b8ce123e2b 100644
--- a/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php
+++ b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php
@@ -5,7 +5,6 @@
 use Behat\Mink\Exception\ElementNotFoundException;
 use Behat\Mink\Exception\ExpectationException;
 use Drupal\Component\Serialization\Json;
-use Drupal\Component\Utility\Html;
 use Drupal\Core\Url;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\Tests\StreamCapturer;
@@ -17,6 +16,7 @@
  * Tests BrowserTestBase functionality.
  *
  * @group browsertestbase
+ * @group #slow
  */
 class BrowserTestBaseTest extends BrowserTestBase {
 
@@ -193,81 +193,6 @@ public function testError() {
     $this->drupalGet('test-error');
   }
 
-  /**
-   * Tests linkExists() with pipe character (|) in locator.
-   *
-   * @see \Drupal\Tests\WebAssert::linkExists()
-   */
-  public function testPipeCharInLocator() {
-    $this->drupalGet('test-pipe-char');
-    $this->assertSession()->linkExists('foo|bar|baz');
-  }
-
-  /**
-   * Tests linkExistsExact() functionality.
-   *
-   * @see \Drupal\Tests\WebAssert::linkExistsExact()
-   */
-  public function testLinkExistsExact() {
-    $this->drupalGet('test-pipe-char');
-    $this->assertSession()->linkExistsExact('foo|bar|baz');
-  }
-
-  /**
-   * Tests linkExistsExact() functionality fail.
-   *
-   * @see \Drupal\Tests\WebAssert::linkExistsExact()
-   */
-  public function testInvalidLinkExistsExact() {
-    $this->drupalGet('test-pipe-char');
-    $this->expectException(ExpectationException::class);
-    $this->expectExceptionMessage('Link with label foo|bar not found');
-    $this->assertSession()->linkExistsExact('foo|bar');
-  }
-
-  /**
-   * Tests linkNotExistsExact() functionality.
-   *
-   * @see \Drupal\Tests\WebAssert::linkNotExistsExact()
-   */
-  public function testLinkNotExistsExact() {
-    $this->drupalGet('test-pipe-char');
-    $this->assertSession()->linkNotExistsExact('foo|bar');
-  }
-
-  /**
-   * Tests responseHeaderDoesNotExist() functionality.
-   *
-   * @see \Drupal\Tests\WebAssert::responseHeaderDoesNotExist()
-   */
-  public function testResponseHeaderDoesNotExist() {
-    $this->drupalGet('test-pipe-char');
-    $this->assertSession()->responseHeaderDoesNotExist('Foo-Bar');
-  }
-
-  /**
-   * Tests linkNotExistsExact() functionality fail.
-   *
-   * @see \Drupal\Tests\WebAssert::linkNotExistsExact()
-   */
-  public function testInvalidLinkNotExistsExact() {
-    $this->drupalGet('test-pipe-char');
-    $this->expectException(ExpectationException::class);
-    $this->expectExceptionMessage('Link with label foo|bar|baz found');
-    $this->assertSession()->linkNotExistsExact('foo|bar|baz');
-  }
-
-  /**
-   * Tests legacy text asserts.
-   */
-  public function testTextAsserts() {
-    $this->drupalGet('test-encoded');
-    $dangerous = 'Bad html <script>alert(123);</script>';
-    $sanitized = Html::escape($dangerous);
-    $this->assertSession()->responseNotContains($dangerous);
-    $this->assertSession()->responseContains($sanitized);
-  }
-
   /**
    * Tests legacy field asserts which use xpath directly.
    */
@@ -410,41 +335,6 @@ public function testFieldAssertsForTextfields() {
     $this->assertSession()->fieldValueEquals('edit-test-textarea-with-newline', "Test text with\nnewline");
   }
 
-  /**
-   * Tests legacy field asserts for button field type.
-   */
-  public function testFieldAssertsForButton() {
-    $this->drupalGet('test-field-xpath');
-
-    // Verify if the test passes with button ID.
-    $this->assertSession()->buttonExists('edit-save');
-    // Verify if the test passes with button Value.
-    $this->assertSession()->buttonExists('Save');
-    // Verify if the test passes with button Name.
-    $this->assertSession()->buttonExists('op');
-
-    // Verify if the test passes with button ID.
-    $this->assertSession()->buttonNotExists('i-do-not-exist');
-    // Verify if the test passes with button Value.
-    $this->assertSession()->buttonNotExists('I do not exist');
-    // Verify if the test passes with button Name.
-    $this->assertSession()->buttonNotExists('no');
-
-    // Test that multiple fields with the same name are validated correctly.
-    $this->assertSession()->buttonExists('duplicate_button');
-    $this->assertSession()->buttonExists('Duplicate button 1');
-    $this->assertSession()->buttonExists('Duplicate button 2');
-    $this->assertSession()->buttonNotExists('Rabbit');
-
-    try {
-      $this->assertSession()->buttonNotExists('Duplicate button 2');
-      $this->fail('The "duplicate_button" field with the value Duplicate button 2 was not found.');
-    }
-    catch (ExpectationException $e) {
-      // Expected exception; just continue testing.
-    }
-  }
-
   /**
    * Tests legacy field asserts for checkbox field type.
    */
@@ -631,48 +521,6 @@ public function testHtkey() {
     $this->assertSession()->statusCodeEquals(403);
   }
 
-  /**
-   * Tests pageContainsNoDuplicateId() functionality.
-   *
-   * @see \Drupal\Tests\WebAssert::pageContainsNoDuplicateId()
-   */
-  public function testPageContainsNoDuplicateId() {
-    $assert_session = $this->assertSession();
-    $this->drupalGet(Url::fromRoute('test_page_test.page_without_duplicate_ids'));
-    $assert_session->pageContainsNoDuplicateId();
-
-    $this->drupalGet(Url::fromRoute('test_page_test.page_with_duplicate_ids'));
-    $this->expectException(ExpectationException::class);
-    $this->expectExceptionMessage('The page contains a duplicate HTML ID "page-element".');
-    $assert_session->pageContainsNoDuplicateId();
-  }
-
-  /**
-   * Tests assertEscaped() and assertUnescaped().
-   *
-   * @see \Drupal\Tests\WebAssert::assertNoEscaped()
-   * @see \Drupal\Tests\WebAssert::assertEscaped()
-   */
-  public function testEscapingAssertions() {
-    $assert = $this->assertSession();
-
-    $this->drupalGet('test-escaped-characters');
-    $assert->assertNoEscaped('<div class="escaped">');
-    $assert->responseContains('<div class="escaped">');
-    $assert->assertEscaped('Escaped: <"\'&>');
-
-    $this->drupalGet('test-escaped-script');
-    $assert->assertNoEscaped('<div class="escaped">');
-    $assert->responseContains('<div class="escaped">');
-    $assert->assertEscaped("<script>alert('XSS');alert(\"XSS\");</script>");
-
-    $this->drupalGet('test-unescaped-script');
-    $assert->assertNoEscaped('<div class="unescaped">');
-    $assert->responseContains('<div class="unescaped">');
-    $assert->responseContains("<script>alert('Marked safe');alert(\"Marked safe\");</script>");
-    $assert->assertNoEscaped("<script>alert('Marked safe');alert(\"Marked safe\");</script>");
-  }
-
   /**
    * Tests that deprecation headers do not get duplicated.
    *
diff --git a/core/tests/Drupal/FunctionalTests/Entity/RevisionDeleteFormTest.php b/core/tests/Drupal/FunctionalTests/Entity/RevisionDeleteFormTest.php
index b956658c14be90eca43559a42be10ee0a6154324..ff8a8ab18b91b02b04534db875e50b2b78196653 100644
--- a/core/tests/Drupal/FunctionalTests/Entity/RevisionDeleteFormTest.php
+++ b/core/tests/Drupal/FunctionalTests/Entity/RevisionDeleteFormTest.php
@@ -12,6 +12,7 @@
  * Tests deleting a revision with revision delete form.
  *
  * @group Entity
+ * @group #slow
  * @coversDefaultClass \Drupal\Core\Entity\Form\RevisionDeleteForm
  */
 class RevisionDeleteFormTest extends BrowserTestBase {
@@ -51,6 +52,7 @@ protected function setUp(): void {
    * @dataProvider providerPageTitle
    */
   public function testPageTitle(string $entityTypeId, string $expectedQuestion): void {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
 
     $entity = $storage->create([
@@ -157,9 +159,10 @@ public function testAccessDeleteDefault(): void {
     $entity->save();
 
     // Reload the entity.
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_revpub');
     /** @var \Drupal\entity_test\Entity\EntityTestRevPub $revision */
-    $revision = \Drupal::entityTypeManager()->getStorage('entity_test_revpub')
-      ->loadRevision($revisionId);
+    $revision = $storage->loadRevision($revisionId);
     // Check default but not latest.
     $this->assertTrue($revision->isDefaultRevision());
     $this->assertFalse($revision->isLatestRevision());
@@ -185,8 +188,9 @@ public function testAccessDeleteNonLatest(): void {
     $entity->save();
 
     // Reload the entity.
-    $revision = \Drupal::entityTypeManager()->getStorage('entity_test_rev')
-      ->loadRevision($revisionId);
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
+    $revision = $storage->loadRevision($revisionId);
     $this->drupalGet($revision->toUrl('revision-delete-form'));
     $this->assertSession()->statusCodeEquals(200);
     $this->assertTrue($revision->access('delete revision', $this->rootUser, FALSE));
@@ -218,6 +222,7 @@ public function testSubmitForm(array $permissions, string $entityTypeId, string
     if (count($permissions) > 0) {
       $this->drupalLogin($this->createUser($permissions));
     }
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
 
     $entity = $storage->create([
diff --git a/core/tests/Drupal/FunctionalTests/Entity/RevisionRevertFormTest.php b/core/tests/Drupal/FunctionalTests/Entity/RevisionRevertFormTest.php
index 2c67d3ab4a984c7684d38bf459ec34950411bbd2..1597137797b312c163167aa273a60ab84474c6c0 100644
--- a/core/tests/Drupal/FunctionalTests/Entity/RevisionRevertFormTest.php
+++ b/core/tests/Drupal/FunctionalTests/Entity/RevisionRevertFormTest.php
@@ -13,6 +13,7 @@
  * Tests reverting a revision with revision revert form.
  *
  * @group Entity
+ * @group #slow
  * @coversDefaultClass \Drupal\Core\Entity\Form\RevisionRevertForm
  */
 class RevisionRevertFormTest extends BrowserTestBase {
@@ -52,6 +53,7 @@ protected function setUp(): void {
    * @dataProvider providerPageTitle
    */
   public function testPageTitle(string $entityTypeId, string $expectedQuestion): void {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
 
     $entity = $storage->create([
@@ -150,8 +152,9 @@ public function testAccessRevertNonLatest(): void {
     $entity->save();
 
     // Reload the entity.
-    $revision = \Drupal::entityTypeManager()->getStorage('entity_test_rev')
-      ->loadRevision($revisionId);
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
+    $revision = $storage->loadRevision($revisionId);
     $this->drupalGet($revision->toUrl('revision-revert-form'));
     $this->assertSession()->statusCodeEquals(200);
     $this->assertTrue($revision->access('revert', $this->rootUser, FALSE));
@@ -181,6 +184,7 @@ public function testSubmitForm(array $permissions, string $entityTypeId, string
     if (count($permissions) > 0) {
       $this->drupalLogin($this->createUser($permissions));
     }
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
 
     $entity = $storage->create([
@@ -303,6 +307,7 @@ public function testPrepareRevision(): void {
     $count = $this->countRevisions($entity->getEntityTypeId());
 
     // Load the revision to be copied.
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
     /** @var \Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog $targetRevision */
     $targetRevision = $storage->loadRevision($targetRevertRevisionId);
diff --git a/core/tests/Drupal/FunctionalTests/Entity/RevisionRouteProviderTest.php b/core/tests/Drupal/FunctionalTests/Entity/RevisionRouteProviderTest.php
index 56062dade42ec941ac8bb72e9b35e8b03ecf8b7c..5626c8f162352c013a7ae80f5a17b4a0655d57d5 100644
--- a/core/tests/Drupal/FunctionalTests/Entity/RevisionRouteProviderTest.php
+++ b/core/tests/Drupal/FunctionalTests/Entity/RevisionRouteProviderTest.php
@@ -54,7 +54,9 @@ public function testRevisionTitle(): void {
     $entity->save();
 
     // Reload the object.
-    $revision = \Drupal::entityTypeManager()->getStorage('entity_test_rev')->loadRevision($revisionId);
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
+    $revision = $storage->loadRevision($revisionId);
     $this->drupalGet($revision->toUrl('revision'));
     $this->assertSession()->responseContains('first revision');
     $this->assertSession()->responseNotContains('second revision');
diff --git a/core/tests/Drupal/FunctionalTests/Entity/RevisionVersionHistoryTest.php b/core/tests/Drupal/FunctionalTests/Entity/RevisionVersionHistoryTest.php
index f05a40abd0adeafbc0f5b817976540bcdc216332..ee7041c43fbad558513dc43ce4efd4cf2da82b41 100644
--- a/core/tests/Drupal/FunctionalTests/Entity/RevisionVersionHistoryTest.php
+++ b/core/tests/Drupal/FunctionalTests/Entity/RevisionVersionHistoryTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\FunctionalTests\Entity;
 
+use Drupal\Core\Entity\Controller\VersionHistoryController;
 use Drupal\entity_test\Entity\EntityTestRev;
 use Drupal\entity_test_revlog\Entity\EntityTestWithRevisionLog;
 use Drupal\Tests\BrowserTestBase;
@@ -10,6 +11,7 @@
  * Tests version history page.
  *
  * @group Entity
+ * @group #slow
  * @coversDefaultClass \Drupal\Core\Entity\Controller\VersionHistoryController
  */
 class RevisionVersionHistoryTest extends BrowserTestBase {
@@ -202,8 +204,9 @@ public function testDescriptionLinkWithAccess(): void {
     $row1Link = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(1) a');
     $this->assertEquals($entity->toUrl()->toString(), $row1Link->getAttribute('href'));
     // Reload revision so object has the properties to build a revision link.
-    $firstRevision = \Drupal::entityTypeManager()->getStorage('entity_test_revlog')
-      ->loadRevision($firstRevisionId);
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = \Drupal::entityTypeManager()->getStorage('entity_test_revlog');
+    $firstRevision = $storage->loadRevision($firstRevisionId);
     $row2Link = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(2) a');
     $this->assertEquals($firstRevision->toUrl('revision')->toString(), $row2Link->getAttribute('href'));
   }
@@ -304,4 +307,44 @@ public function testOperationDeleteRevision(): void {
     $this->assertSession()->elementsCount('css', 'table tbody tr', 3);
   }
 
+  /**
+   * Test revisions are paginated.
+   */
+  public function testRevisionsPagination(): void {
+    /** @var \Drupal\entity_test\Entity\EntityTestRev $entity */
+    $entity = EntityTestRev::create([
+      'type' => 'entity_test_rev',
+      'name' => 'view all revisions,view revision',
+    ]);
+    $entity->save();
+
+    $firstRevisionId = $entity->getRevisionId();
+
+    for ($i = 0; $i < VersionHistoryController::REVISIONS_PER_PAGE; $i++) {
+      $entity->setNewRevision(TRUE);
+      // We need to change something on the entity for it to be considered a new
+      // revision to display. We need "view all revisions" and "view revision"
+      // in a comma separated string to grant access.
+      $entity->setName('view all revisions,view revision,' . $i)->save();
+    }
+
+    $this->drupalGet($entity->toUrl('version-history'));
+    $this->assertSession()->elementsCount('css', 'table tbody tr', VersionHistoryController::REVISIONS_PER_PAGE);
+    $this->assertSession()->elementExists('css', '.pager');
+
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
+    $firstRevision = $storage->loadRevision($firstRevisionId);
+    $secondRevision = $storage->loadRevision($firstRevisionId + 1);
+    // We should see everything up to the second revision, but not the first.
+    $this->assertSession()->linkByHrefExists($secondRevision->toUrl('revision')->toString());
+    $this->assertSession()->linkByHrefNotExists($firstRevision->toUrl('revision')->toString());
+    // The next page should show just the first revision.
+    $this->clickLink('Go to next page');
+    $this->assertSession()->elementsCount('css', 'table tbody tr', 1);
+    $this->assertSession()->elementExists('css', '.pager');
+    $this->assertSession()->linkByHrefNotExists($secondRevision->toUrl('revision')->toString());
+    $this->assertSession()->linkByHrefExists($firstRevision->toUrl('revision')->toString());
+  }
+
 }
diff --git a/core/tests/Drupal/FunctionalTests/Entity/RevisionVersionHistoryTranslatableTest.php b/core/tests/Drupal/FunctionalTests/Entity/RevisionVersionHistoryTranslatableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4550597e6f6d24379f7d92e163981128a9806880
--- /dev/null
+++ b/core/tests/Drupal/FunctionalTests/Entity/RevisionVersionHistoryTranslatableTest.php
@@ -0,0 +1,84 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\FunctionalTests\Entity;
+
+use Drupal\entity_test_revlog\Entity\EntityTestMulWithRevisionLog;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests version history page with translations.
+ *
+ * @group Entity
+ * @coversDefaultClass \Drupal\Core\Entity\Controller\VersionHistoryController
+ */
+final class RevisionVersionHistoryTranslatableTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'entity_test_revlog',
+    'content_translation',
+    'language',
+    'user',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    ConfigurableLanguage::createFromLangcode('es')->save();
+
+    // Rebuild the container so that the new languages are picked up by services
+    // that hold a list of languages.
+    $this->rebuildContainer();
+  }
+
+  /**
+   * Tests the version history page for translations.
+   */
+  public function testVersionHistoryTranslations(): void {
+    $label = 'view all revisions,revert,delete revision';
+    $entity = EntityTestMulWithRevisionLog::create([
+      'name' => $label,
+      'type' => 'entity_test_mul_revlog',
+    ]);
+    $entity->addTranslation('es', ['label' => 'version history test translations es']);
+    $entity->save();
+
+    $firstRevisionId = $entity->getRevisionId();
+
+    $entity->setNewRevision();
+    $entity->setName($label . ',2')
+      ->save();
+
+    $this->drupalGet($entity->toUrl('version-history'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->elementsCount('css', 'table tbody tr', 2);
+
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+    $storage = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
+    $firstRevision = $storage->loadRevision($firstRevisionId);
+
+    $this->assertSession()->linkByHrefExists($firstRevision->toUrl('revision-revert-form')->toString());
+    $this->assertSession()->linkByHrefExists($firstRevision->toUrl('revision-delete-form')->toString());
+    $this->assertSession()->linkByHrefNotExists($firstRevision->getTranslation('es')->toUrl('revision-revert-form')->toString());
+    $this->assertSession()->linkByHrefNotExists($firstRevision->getTranslation('es')->toUrl('revision-delete-form')->toString());
+
+    $this->drupalGet($entity->getTranslation('es')->toUrl('version-history'));
+    $this->assertSession()->linkByHrefNotExistsExact($firstRevision->toUrl('revision-revert-form')->toString());
+    $this->assertSession()->linkByHrefNotExistsExact($firstRevision->toUrl('revision-delete-form')->toString());
+    $this->assertSession()->linkByHrefExists($firstRevision->getTranslation('es')->toUrl('revision-revert-form')->toString());
+    $this->assertSession()->linkByHrefExists($firstRevision->getTranslation('es')->toUrl('revision-delete-form')->toString());
+  }
+
+}
diff --git a/core/tests/Drupal/FunctionalTests/Entity/RevisionViewTest.php b/core/tests/Drupal/FunctionalTests/Entity/RevisionViewTest.php
index 52b34cdacb3bb3d0fff4c1ddb173105566420ed3..f728b6c95802243125192fec7b81c72db34fafc6 100644
--- a/core/tests/Drupal/FunctionalTests/Entity/RevisionViewTest.php
+++ b/core/tests/Drupal/FunctionalTests/Entity/RevisionViewTest.php
@@ -51,6 +51,7 @@ protected function setUp(): void {
    * @dataProvider providerRevisionPage
    */
   public function testRevisionPage(string $entityTypeId, string $expectedPageTitle): void {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage($entityTypeId);
 
     // Add a field to test revision page output.
diff --git a/core/tests/Drupal/FunctionalTests/Libraries/JqueryUiLibraryAssetsTest.php b/core/tests/Drupal/FunctionalTests/Libraries/JqueryUiLibraryAssetsTest.php
index e276f4a6d09b7dd01a88ef41ab13e198b7364e54..e9bcedaa2f3ddb1231052751251ec5477c9e18ca 100644
--- a/core/tests/Drupal/FunctionalTests/Libraries/JqueryUiLibraryAssetsTest.php
+++ b/core/tests/Drupal/FunctionalTests/Libraries/JqueryUiLibraryAssetsTest.php
@@ -56,6 +56,7 @@ protected function setUp(): void {
 
     // All the core libraries that use jQuery UI assets.
     $libraries_to_check = [
+      'internal.jquery_ui',
       'drupal.autocomplete',
       'drupal.dialog',
     ];
diff --git a/core/tests/Drupal/FunctionalTests/WebAssertTest.php b/core/tests/Drupal/FunctionalTests/WebAssertTest.php
index 6b13dc3184450abb6b6105b709acaffedcff6cd6..9739038473c5ae8a18188f5560adcd9984506686 100644
--- a/core/tests/Drupal/FunctionalTests/WebAssertTest.php
+++ b/core/tests/Drupal/FunctionalTests/WebAssertTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\FunctionalTests;
 
 use Behat\Mink\Exception\ExpectationException;
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Url;
 use Drupal\Tests\BrowserTestBase;
 use Behat\Mink\Exception\ResponseTextException;
@@ -12,6 +13,7 @@
  * Tests WebAssert functionality.
  *
  * @group browsertestbase
+ * @group #slow
  * @coversDefaultClass \Drupal\Tests\WebAssert
  */
 class WebAssertTest extends BrowserTestBase {
@@ -133,4 +135,250 @@ public function testAddressNotEqualsException(): void {
     $this->assertSession()->addressNotEquals('test-page?a=b&c=d');
   }
 
+  /**
+   * Tests linkExists() with pipe character (|) in locator.
+   *
+   * @covers ::linkExists
+   */
+  public function testPipeCharInLocator() {
+    $this->drupalGet('test-pipe-char');
+    $this->assertSession()->linkExists('foo|bar|baz');
+  }
+
+  /**
+   * Tests linkExistsExact() functionality.
+   *
+   * @covers ::linkExistsExact
+   */
+  public function testLinkExistsExact() {
+    $this->drupalGet('test-pipe-char');
+    $this->assertSession()->linkExistsExact('foo|bar|baz');
+  }
+
+  /**
+   * Tests linkExistsExact() functionality fail.
+   *
+   * @covers ::linkExistsExact
+   */
+  public function testInvalidLinkExistsExact() {
+    $this->drupalGet('test-pipe-char');
+    $this->expectException(ExpectationException::class);
+    $this->expectExceptionMessage('Link with label foo|bar not found');
+    $this->assertSession()->linkExistsExact('foo|bar');
+  }
+
+  /**
+   * Tests linkNotExistsExact() functionality.
+   *
+   * @covers ::linkNotExistsExact
+   */
+  public function testLinkNotExistsExact() {
+    $this->drupalGet('test-pipe-char');
+    $this->assertSession()->linkNotExistsExact('foo|bar');
+  }
+
+  /**
+   * Tests linkNotExistsExact() functionality fail.
+   *
+   * @covers ::linkNotExistsExact
+   */
+  public function testInvalidLinkNotExistsExact() {
+    $this->drupalGet('test-pipe-char');
+    $this->expectException(ExpectationException::class);
+    $this->expectExceptionMessage('Link with label foo|bar|baz found');
+    $this->assertSession()->linkNotExistsExact('foo|bar|baz');
+  }
+
+  /**
+   * Tests linkExistsByHref() functionality.
+   *
+   * @covers ::linkByHrefExists
+   */
+  public function testLinkByHrefExists(): void {
+    $this->drupalGet('test-page');
+    // Partial matching.
+    $this->assertSession()->linkByHrefExists('/user');
+    // Full matching.
+    $this->assertSession()->linkByHrefExists('/user/login');
+  }
+
+  /**
+   * Tests linkExistsByHref() functionality fail.
+   *
+   * @covers ::linkByHrefExists
+   */
+  public function testInvalidLinkByHrefExists(): void {
+    $this->drupalGet('test-page');
+    $this->expectException(ExpectationException::class);
+    $this->assertSession()->linkByHrefExists('/foo');
+  }
+
+  /**
+   * Tests linkByHrefNotExists() functionality.
+   *
+   * @covers ::linkByHrefNotExists
+   */
+  public function testLinkByHrefNotExists(): void {
+    $this->drupalGet('test-page');
+    $this->assertSession()->linkByHrefNotExists('/foo');
+  }
+
+  /**
+   * Tests LinkByHrefNotExists() functionality fail partial match.
+   *
+   * @covers ::linkByHrefNotExists
+   */
+  public function testInvalidLinkByHrefNotExistsPartial(): void {
+    $this->drupalGet('test-page');
+    $this->expectException(ExpectationException::class);
+    $this->assertSession()->linkByHrefNotExists('/user');
+  }
+
+  /**
+   * Tests LinkByHrefNotExists() functionality fail full match.
+   *
+   * @covers ::linkByHrefNotExists
+   */
+  public function testInvalidLinkByHrefNotExistsFull(): void {
+    $this->drupalGet('test-page');
+    $this->expectException(ExpectationException::class);
+    $this->assertSession()->linkByHrefNotExists('/user/login');
+  }
+
+  /**
+   * Tests linkExistsByHref() functionality.
+   *
+   * @covers ::linkByHrefExistsExact
+   */
+  public function testLinkByHrefExistsExact(): void {
+    $this->drupalGet('test-page');
+    $this->assertSession()->linkByHrefExistsExact('/user/login');
+  }
+
+  /**
+   * Tests linkByHrefExistsExact() functionality fail.
+   *
+   * @covers ::linkByHrefExistsExact
+   */
+  public function testInvalidLinkByHrefExistsExact(): void {
+    $this->drupalGet('test-page');
+    $this->expectException(ExpectationException::class);
+    $this->assertSession()->linkByHrefExistsExact('/foo');
+  }
+
+  /**
+   * Tests linkByHrefNotExistsExact() functionality.
+   *
+   * @covers ::linkByHrefNotExistsExact
+   */
+  public function testLinkByHrefNotExistsExact(): void {
+    $this->drupalGet('test-page');
+    $this->assertSession()->linkByHrefNotExistsExact('/foo');
+  }
+
+  /**
+   * Tests linkByHrefNotExistsExact() functionality fail.
+   *
+   * @covers ::linkByHrefNotExistsExact
+   */
+  public function testInvalidLinkByHrefNotExistsExact(): void {
+    $this->drupalGet('test-page');
+    $this->expectException(ExpectationException::class);
+    $this->assertSession()->linkByHrefNotExistsExact('/user/login');
+  }
+
+  /**
+   * Tests legacy text asserts.
+   *
+   * @covers ::responseContains
+   * @covers ::responseNotContains
+   */
+  public function testTextAsserts() {
+    $this->drupalGet('test-encoded');
+    $dangerous = 'Bad html <script>alert(123);</script>';
+    $sanitized = Html::escape($dangerous);
+    $this->assertSession()->responseNotContains($dangerous);
+    $this->assertSession()->responseContains($sanitized);
+  }
+
+  /**
+   * Tests legacy field asserts for button field type.
+   *
+   * @covers ::buttonExists
+   * @covers ::buttonNotExists
+   */
+  public function testFieldAssertsForButton() {
+    $this->drupalGet('test-field-xpath');
+
+    // Verify if the test passes with button ID.
+    $this->assertSession()->buttonExists('edit-save');
+    // Verify if the test passes with button Value.
+    $this->assertSession()->buttonExists('Save');
+    // Verify if the test passes with button Name.
+    $this->assertSession()->buttonExists('op');
+
+    // Verify if the test passes with button ID.
+    $this->assertSession()->buttonNotExists('i-do-not-exist');
+    // Verify if the test passes with button Value.
+    $this->assertSession()->buttonNotExists('I do not exist');
+    // Verify if the test passes with button Name.
+    $this->assertSession()->buttonNotExists('no');
+
+    // Test that multiple fields with the same name are validated correctly.
+    $this->assertSession()->buttonExists('duplicate_button');
+    $this->assertSession()->buttonExists('Duplicate button 1');
+    $this->assertSession()->buttonExists('Duplicate button 2');
+    $this->assertSession()->buttonNotExists('Rabbit');
+
+    try {
+      $this->assertSession()->buttonNotExists('Duplicate button 2');
+      $this->fail('The "duplicate_button" field with the value Duplicate button 2 was not found.');
+    }
+    catch (ExpectationException $e) {
+      // Expected exception; just continue testing.
+    }
+  }
+
+  /**
+   * Tests pageContainsNoDuplicateId() functionality.
+   *
+   * @covers ::pageContainsNoDuplicateId
+   */
+  public function testPageContainsNoDuplicateId() {
+    $assert_session = $this->assertSession();
+    $this->drupalGet(Url::fromRoute('test_page_test.page_without_duplicate_ids'));
+    $assert_session->pageContainsNoDuplicateId();
+
+    $this->drupalGet(Url::fromRoute('test_page_test.page_with_duplicate_ids'));
+    $this->expectException(ExpectationException::class);
+    $this->expectExceptionMessage('The page contains a duplicate HTML ID "page-element".');
+    $assert_session->pageContainsNoDuplicateId();
+  }
+
+  /**
+   * Tests assertEscaped() and assertUnescaped().
+   *
+   * @covers ::assertNoEscaped
+   * @covers ::assertEscaped
+   */
+  public function testEscapingAssertions() {
+    $assert = $this->assertSession();
+
+    $this->drupalGet('test-escaped-characters');
+    $assert->assertNoEscaped('<div class="escaped">');
+    $assert->responseContains('<div class="escaped">');
+    $assert->assertEscaped('Escaped: <"\'&>');
+
+    $this->drupalGet('test-escaped-script');
+    $assert->assertNoEscaped('<div class="escaped">');
+    $assert->responseContains('<div class="escaped">');
+    $assert->assertEscaped("<script>alert('XSS');alert(\"XSS\");</script>");
+
+    $this->drupalGet('test-unescaped-script');
+    $assert->assertNoEscaped('<div class="unescaped">');
+    $assert->responseContains('<div class="unescaped">');
+    $assert->responseContains("<script>alert('Marked safe');alert(\"Marked safe\");</script>");
+    $assert->assertNoEscaped("<script>alert('Marked safe');alert(\"Marked safe\");</script>");
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php b/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
index e476ce54f16d9bcfdf630b8032bee5d7dcc29e7f..79d32d37167d50eea26f40a361230ec2c19db9a4 100644
--- a/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Cache/DatabaseBackendTest.php
@@ -53,6 +53,15 @@ public function testSetGet() {
     $cached_value_short = $this->randomMachineName();
     $backend->set($cid_short, $cached_value_short);
     $this->assertSame($cached_value_short, $backend->get($cid_short)->data, "Backend contains the correct value for short, non-ASCII cache id.");
+
+    // Set multiple items to test exceeding the chunk size.
+    $backend->deleteAll();
+    $items = [];
+    for ($i = 0; $i <= DatabaseBackend::MAX_ITEMS_PER_CACHE_SET; $i++) {
+      $items["test$i"]['data'] = $i;
+    }
+    $backend->setMultiple($items);
+    $this->assertSame(DatabaseBackend::MAX_ITEMS_PER_CACHE_SET + 1, $this->getNumRows());
   }
 
   /**
diff --git a/core/tests/Drupal/KernelTests/Core/Cache/EndOfTransactionQueriesTest.php b/core/tests/Drupal/KernelTests/Core/Cache/EndOfTransactionQueriesTest.php
index 8d8b1a1400faa2bd9c17c2f736370233a34c61bb..40d804893f4a842e89aaa61137b35fca834fa5e0 100644
--- a/core/tests/Drupal/KernelTests/Core/Cache/EndOfTransactionQueriesTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Cache/EndOfTransactionQueriesTest.php
@@ -68,6 +68,14 @@ public function testEntitySave(): void {
 
     $executed_statements = [];
     foreach (Database::getLog('testEntitySave') as $log) {
+      // Exclude transaction related statements from the log.
+      if (
+        str_starts_with($log['query'], 'ROLLBACK TO SAVEPOINT ') ||
+        str_starts_with($log['query'], 'RELEASE SAVEPOINT ') ||
+        str_starts_with($log['query'], 'SAVEPOINT ')
+      ) {
+        continue;
+      }
       $executed_statements[] = $log['query'];
     }
     $last_statement_index = max(array_keys($executed_statements));
diff --git a/core/tests/Drupal/KernelTests/Core/Common/LegacyCommonTest.php b/core/tests/Drupal/KernelTests/Core/Common/LegacyCommonTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b4ca440472426cb9ab671b9c1dbe40359bf26cab
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Common/LegacyCommonTest.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Common;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests deprecated legacy functions in common.inc.
+ *
+ * @group Common
+ * @group legacy
+ */
+class LegacyCommonTest extends KernelTestBase {
+
+  /**
+   * Tests deprecation of the format_size() function.
+   */
+  public function testFormatSizeDeprecation(): void {
+    $this->expectDeprecation('format_size() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\Core\StringTranslation\ByteSizeMarkup::create($size, $langcode) instead. See https://www.drupal.org/node/2999981');
+    $size = format_size(4053371676);
+    $this->assertEquals('3.77 GB', $size);
+    $this->assertEquals('@size GB', $size->getUntranslatedString());
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Common/SizeTest.php b/core/tests/Drupal/KernelTests/Core/Common/SizeTest.php
deleted file mode 100644
index 77e70784ee34829961f161e41fbcf411f2639199..0000000000000000000000000000000000000000
--- a/core/tests/Drupal/KernelTests/Core/Common/SizeTest.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-namespace Drupal\KernelTests\Core\Common;
-
-use Drupal\Component\Utility\Bytes;
-use Drupal\KernelTests\KernelTestBase;
-
-/**
- * Tests format_size().
- *
- * @group Common
- */
-class SizeTest extends KernelTestBase {
-
-  /**
-   * Checks that format_size() returns the expected string.
-   *
-   * @dataProvider providerTestCommonFormatSize
-   */
-  public function testCommonFormatSize($expected, $input) {
-    $size = format_size($input, NULL);
-    $this->assertEquals($expected, $size);
-  }
-
-  /**
-   * Provides a list of byte size to test.
-   */
-  public function providerTestCommonFormatSize() {
-    $kb = Bytes::KILOBYTE;
-    return [
-      ['0 bytes', 0],
-      ['1 byte', 1],
-      ['-1 bytes', -1],
-      ['2 bytes', 2],
-      ['-2 bytes', -2],
-      ['1023 bytes', $kb - 1],
-      ['1 KB', $kb],
-      ['1 MB', pow($kb, 2)],
-      ['1 GB', pow($kb, 3)],
-      ['1 TB', pow($kb, 4)],
-      ['1 PB', pow($kb, 5)],
-      ['1 EB', pow($kb, 6)],
-      ['1 ZB', pow($kb, 7)],
-      ['1 YB', pow($kb, 8)],
-      ['1024 YB', pow($kb, 9)],
-      // Rounded to 1 MB - not 1000 or 1024 kilobytes
-      ['1 MB', ($kb * $kb) - 1],
-      ['-1 MB', -(($kb * $kb) - 1)],
-      // Decimal Megabytes
-      ['3.46 MB', 3623651],
-      ['3.77 GB', 4053371676],
-      // Decimal Petabytes
-      ['59.72 PB', 67234178751368124],
-      // Decimal Yottabytes
-      ['194.67 YB', 235346823821125814962843827],
-    ];
-  }
-
-}
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php
index 45802a579c1a6a5eb3afb9e1c12917b344cb7c85..a1cd756fa35d5b6c1a18664f50575a634e4cca0f 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php
@@ -134,10 +134,10 @@ public function testInvalidMachineNameCharacters(string $machine_name, bool $is_
       $expected_errors = [$id_key => sprintf('The <em class="placeholder">&quot;%s&quot;</em> machine name is not valid.', $machine_name)];
     }
 
-    $this->entity->set(
-      $id_key,
-      $machine_name
-    );
+    // Config entity IDs are immutable by default.
+    $expected_errors[''] = "The '$id_key' property cannot be changed.";
+
+    $this->entity->set($id_key, $machine_name);
     $this->assertValidationErrors($expected_errors);
   }
 
@@ -152,13 +152,13 @@ public function testMachineNameLength(): void {
     $this->assertGreaterThan(0, $max_length);
 
     $id_key = $this->entity->getEntityType()->getKey('id');
-    $this->entity->set(
-      $id_key,
-      $this->randomMachineName($max_length + 2)
-    );
-    $this->assertValidationErrors([
+    $expected_errors = [
       $id_key => 'This value is too long. It should have <em class="placeholder">' . $max_length . '</em> characters or less.',
-    ]);
+      // Config entity IDs are immutable by default.
+      '' => "The '$id_key' property cannot be changed.",
+    ];
+    $this->entity->set($id_key, $this->randomMachineName($max_length + 2));
+    $this->assertValidationErrors($expected_errors);
   }
 
   /**
@@ -356,18 +356,22 @@ protected function assertValidationErrors(array $expected_messages): void {
 
     $actual_messages = [];
     foreach ($violations as $violation) {
-      if (!isset($actual_messages[$violation->getPropertyPath()])) {
-        $actual_messages[$violation->getPropertyPath()] = (string) $violation->getMessage();
+      $property_path = $violation->getPropertyPath();
+
+      if (!isset($actual_messages[$property_path])) {
+        $actual_messages[$property_path] = (string) $violation->getMessage();
       }
       else {
         // Transform value from string to array.
-        if (is_string($actual_messages[$violation->getPropertyPath()])) {
-          $actual_messages[$violation->getPropertyPath()] = (array) $actual_messages[$violation->getPropertyPath()];
+        if (is_string($actual_messages[$property_path])) {
+          $actual_messages[$property_path] = (array) $actual_messages[$violation->getPropertyPath()];
         }
         // And append.
-        $actual_messages[$violation->getPropertyPath()][] = (string) $violation->getMessage();
+        $actual_messages[$property_path][] = (string) $violation->getMessage();
       }
     }
+    ksort($expected_messages);
+    ksort($actual_messages);
     $this->assertSame($expected_messages, $actual_messages);
   }
 
@@ -420,4 +424,26 @@ public function testLangcode(): void {
     $this->assertValidationErrors([]);
   }
 
+  /**
+   * Tests that immutable properties cannot be changed.
+   *
+   * @param mixed[] $valid_values
+   *   (optional) The values to set for the immutable properties, keyed by name.
+   *   This should be used if the immutable properties can only accept certain
+   *   values, e.g. valid plugin IDs.
+   */
+  public function testImmutableProperties(array $valid_values = []): void {
+    $constraints = $this->entity->getEntityType()->getConstraints();
+    $this->assertNotEmpty($constraints['ImmutableProperties'], 'All config entities should have at least one immutable ID property.');
+
+    foreach ($constraints['ImmutableProperties'] as $property_name) {
+      $original_value = $this->entity->get($property_name);
+      $this->entity->set($property_name, $valid_values[$property_name] ?? $this->randomMachineName());
+      $this->assertValidationErrors([
+        '' => "The '$property_name' property cannot be changed.",
+      ]);
+      $this->entity->set($property_name, $original_value);
+    }
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigFileContentTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigFileContentTest.php
index 8a65787cf8de5c3b2fb0fdab858c0004c281a60b..db8181776ec4d153cb46bc1a5ab521ad92c4bc65 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigFileContentTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigFileContentTest.php
@@ -207,9 +207,9 @@ public function testSerialization() {
     ];
 
     // Encode and write, and reload and decode the configuration data.
-    $filestorage = new FileStorage(Settings::get('config_sync_directory'));
-    $filestorage->write($name, $config_data);
-    $config_parsed = $filestorage->read($name);
+    $file_storage = new FileStorage(Settings::get('config_sync_directory'));
+    $file_storage->write($name, $config_data);
+    $config_parsed = $file_storage->read($name);
 
     $key = 'numeric keys';
     $this->assertSame($config_data[$key], $config_parsed[$key]);
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
index 8a0d99bfcde1fa66d85bfdaddf5d1e5ef51d3429..f5f9df5385fe78477943d00a89ac12dc9d912a9a 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
@@ -352,6 +352,8 @@ public function testSchemaData() {
 
   /**
    * Tests configuration value data type enforcement using schemas.
+   *
+   * @group legacy
    */
   public function testConfigSaveWithSchema() {
     $untyped_values = [
@@ -399,6 +401,7 @@ public function testConfigSaveWithSchema() {
     ];
 
     // Save config which has a schema that enforces types.
+    $this->expectDeprecation("The definition for the 'config_schema_test.schema_data_types.sequence_bc' sequence declares the type of its items in a way that is deprecated in drupal:8.0.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/2442603");
     $this->config('config_schema_test.schema_data_types')
       ->setData($untyped_to_typed)
       ->save();
diff --git a/core/tests/Drupal/KernelTests/Core/Database/DeleteTruncateTest.php b/core/tests/Drupal/KernelTests/Core/Database/DeleteTruncateTest.php
index 689d40ac9a6613c0411fc5a98a07743ce06581b7..ca28d42c324093cacdcae17c9ec9084d10db6a01 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/DeleteTruncateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/DeleteTruncateTest.php
@@ -130,7 +130,7 @@ public function testTruncateTransactionRollback() {
 
     // Roll back the transaction, and check that we are back to status before
     // insert and truncate.
-    $this->connection->rollBack();
+    $transaction->rollBack();
     $this->assertFalse($this->connection->inTransaction());
     $num_records_after = $this->connection->select('test')->countQuery()->execute()->fetchField();
     $this->assertEquals($num_records_before, $num_records_after);
diff --git a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php
index ebbf60826ad1400bdb6e4096a621a75c368a5fa8..b0e9fc3e9b6ae05cc1a03dd36794d9a4c2d9973f 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/DriverSpecificTransactionTestBase.php
@@ -2,9 +2,10 @@
 
 namespace Drupal\KernelTests\Core\Database;
 
-use Drupal\Component\Render\FormattableMarkup;
-use Drupal\Core\Database\TransactionOutOfOrderException;
+use Drupal\Core\Database\TransactionExplicitCommitNotAllowedException;
+use Drupal\Core\Database\TransactionNameNonUniqueException;
 use Drupal\Core\Database\TransactionNoActiveException;
+use Drupal\Core\Database\TransactionOutOfOrderException;
 use PHPUnit\Framework\Error\Warning;
 
 /**
@@ -34,6 +35,11 @@
  */
 class DriverSpecificTransactionTestBase extends DriverSpecificDatabaseTestBase {
 
+  /**
+   * Keeps track of the post-transaction callback action executed.
+   */
+  protected ?string $postTransactionCallbackAction = NULL;
+
   /**
    * Encapsulates a transaction's "inner layer" with an "outer layer".
    *
@@ -57,7 +63,7 @@ class DriverSpecificTransactionTestBase extends DriverSpecificDatabaseTestBase {
    *   Whether to execute a DDL statement during the inner transaction.
    */
   protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
-    $depth = $this->connection->transactionDepth();
+    $depth = $this->connection->transactionManager()->stackDepth();
     $txn = $this->connection->startTransaction();
 
     // Insert a single row into the testing table.
@@ -80,7 +86,7 @@ protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statem
       // Roll back the transaction, if requested.
       // This rollback should propagate to the last savepoint.
       $txn->rollBack();
-      $this->assertSame($depth, $this->connection->transactionDepth(), 'Transaction has rolled back to the last savepoint after calling rollBack().');
+      $this->assertSame($depth, $this->connection->transactionManager()->stackDepth(), 'Transaction has rolled back to the last savepoint after calling rollBack().');
     }
   }
 
@@ -98,14 +104,14 @@ protected function transactionOuterLayer($suffix, $rollback = FALSE, $ddl_statem
    *   Whether to execute a DDL statement during the transaction.
    */
   protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statement = FALSE) {
-    $depth = $this->connection->transactionDepth();
+    $depth = $this->connection->transactionManager()->stackDepth();
     // Start a transaction. If we're being called from ->transactionOuterLayer,
     // then we're already in a transaction. Normally, that would make starting
     // a transaction here dangerous, but the database API handles this problem
     // for us by tracking the nesting and avoiding the danger.
     $txn = $this->connection->startTransaction();
 
-    $depth2 = $this->connection->transactionDepth();
+    $depth2 = $this->connection->transactionManager()->stackDepth();
     $this->assertGreaterThan($depth, $depth2, 'Transaction depth has increased with new transaction.');
 
     // Insert a single row into the testing table.
@@ -138,7 +144,7 @@ protected function transactionInnerLayer($suffix, $rollback = FALSE, $ddl_statem
       // Roll back the transaction, if requested.
       // This rollback should propagate to the last savepoint.
       $txn->rollBack();
-      $this->assertSame($depth, $this->connection->transactionDepth(), 'Transaction has rolled back to the last savepoint after calling rollBack().');
+      $this->assertSame($depth, $this->connection->transactionManager()->stackDepth(), 'Transaction has rolled back to the last savepoint after calling rollBack().');
     }
   }
 
@@ -267,7 +273,7 @@ public function testTransactionWithDdlStatement() {
       try {
         // Rollback the outer transaction.
         $transaction->rollBack();
-        // @see \Drupal\mysql\Driver\Database\mysql\Connection::rollBack()
+        // @see \Drupal\mysql\Driver\Database\mysql\TransactionManager::rollbackClientTransaction()
         $this->fail('Rolling back a transaction containing DDL should produce a warning.');
       }
       catch (Warning $warning) {
@@ -313,6 +319,7 @@ protected function executeDDLStatement() {
   protected function cleanUp() {
     $this->connection->truncate('test')
       ->execute();
+    $this->postTransactionCallbackAction = NULL;
   }
 
   /**
@@ -326,11 +333,8 @@ protected function cleanUp() {
    * @internal
    */
   public function assertRowPresent(string $name, string $message = NULL): void {
-    if (!isset($message)) {
-      $message = new FormattableMarkup('Row %name is present.', ['%name' => $name]);
-    }
     $present = (boolean) $this->connection->query('SELECT 1 FROM {test} WHERE [name] = :name', [':name' => $name])->fetchField();
-    $this->assertTrue($present, $message);
+    $this->assertTrue($present, $message ?? "Row '{$name}' should be present, but it actually does not exist.");
   }
 
   /**
@@ -344,11 +348,8 @@ public function assertRowPresent(string $name, string $message = NULL): void {
    * @internal
    */
   public function assertRowAbsent(string $name, string $message = NULL): void {
-    if (!isset($message)) {
-      $message = new FormattableMarkup('Row %name is absent.', ['%name' => $name]);
-    }
     $present = (boolean) $this->connection->query('SELECT 1 FROM {test} WHERE [name] = :name', [':name' => $name])->fetchField();
-    $this->assertFalse($present, $message);
+    $this->assertFalse($present, $message ?? "Row '{$name}' should be absent, but it actually exists.");
   }
 
   /**
@@ -575,4 +576,132 @@ public function testQueryFailureInTransaction() {
     $this->assertEquals('24', $saved_age);
   }
 
+  /**
+   * Tests releasing a savepoint before last is safe.
+   */
+  public function testReleaseIntermediateSavepoint(): void {
+    $transaction = $this->connection->startTransaction();
+    $this->assertSame(1, $this->connection->transactionManager()->stackDepth());
+    $savepoint1 = $this->connection->startTransaction();
+    $this->assertSame(2, $this->connection->transactionManager()->stackDepth());
+    $savepoint2 = $this->connection->startTransaction();
+    $this->assertSame(3, $this->connection->transactionManager()->stackDepth());
+    $savepoint3 = $this->connection->startTransaction();
+    $this->assertSame(4, $this->connection->transactionManager()->stackDepth());
+    $savepoint4 = $this->connection->startTransaction();
+    $this->assertSame(5, $this->connection->transactionManager()->stackDepth());
+    $this->insertRow('row');
+    unset($savepoint2);
+    $this->assertSame(2, $this->connection->transactionManager()->stackDepth());
+    $this->assertRowPresent('row');
+    unset($savepoint1);
+    unset($transaction);
+    $this->assertFalse($this->connection->inTransaction());
+    $this->assertRowPresent('row');
+  }
+
+  /**
+   * Tests for transaction names.
+   */
+  public function testTransactionName(): void {
+    $transaction = $this->connection->startTransaction();
+    $this->assertSame('drupal_transaction', $transaction->name());
+
+    $savepoint1 = $this->connection->startTransaction();
+    $this->assertSame('savepoint_1', $savepoint1->name());
+
+    $this->expectException(TransactionNameNonUniqueException::class);
+    $this->expectExceptionMessage("savepoint_1 is already in use.");
+    $savepointFailure = $this->connection->startTransaction('savepoint_1');
+  }
+
+  /**
+   * Tests that adding a post-transaction callback fails with no transaction.
+   */
+  public function testRootTransactionEndCallbackAddedWithoutTransaction(): void {
+    $this->expectException(\LogicException::class);
+    $this->connection->transactionManager()->addPostTransactionCallback([$this, 'rootTransactionCallback']);
+  }
+
+  /**
+   * Tests post-transaction callback executes after transaction commit.
+   */
+  public function testRootTransactionEndCallbackCalledOnCommit(): void {
+    $this->cleanUp();
+    $transaction = $this->connection->startTransaction();
+    $this->connection->transactionManager()->addPostTransactionCallback([$this, 'rootTransactionCallback']);
+    $this->insertRow('row');
+    $this->assertNull($this->postTransactionCallbackAction);
+    unset($transaction);
+    $this->assertSame('rtcCommit', $this->postTransactionCallbackAction);
+    $this->assertRowPresent('row');
+    $this->assertRowPresent('rtcCommit');
+  }
+
+  /**
+   * Tests post-transaction callback executes after transaction rollback.
+   */
+  public function testRootTransactionEndCallbackCalledOnRollback(): void {
+    $this->cleanUp();
+    $transaction = $this->connection->startTransaction();
+    $this->connection->transactionManager()->addPostTransactionCallback([$this, 'rootTransactionCallback']);
+    $this->insertRow('row');
+    $this->assertNull($this->postTransactionCallbackAction);
+    $transaction->rollBack();
+    $this->assertSame('rtcRollback', $this->postTransactionCallbackAction);
+    unset($transaction);
+    $this->assertRowAbsent('row');
+    // The row insert should be missing since the client rollback occurs after
+    // the processing of the callbacks.
+    $this->assertRowAbsent('rtcRollback');
+  }
+
+  /**
+   * A post-transaction callback for testing purposes.
+   */
+  public function rootTransactionCallback(bool $success): void {
+    $this->postTransactionCallbackAction = $success ? 'rtcCommit' : 'rtcRollback';
+    $this->insertRow($this->postTransactionCallbackAction);
+  }
+
+  /**
+   * Tests deprecation of Connection methods.
+   *
+   * @group legacy
+   */
+  public function testConnectionDeprecations(): void {
+    $this->cleanUp();
+    $transaction = $this->connection->startTransaction();
+    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::transactionDepth() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not access the transaction stack depth, it is an implementation detail. See https://www.drupal.org/node/3381002');
+    $this->assertSame(1, $this->connection->transactionDepth());
+    $this->insertRow('row');
+    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::rollBack() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not rollback the connection, roll back the Transaction objects instead. See https://www.drupal.org/node/3381002');
+    $this->connection->rollback();
+    $transaction = NULL;
+    $this->assertRowAbsent('row');
+
+    $this->cleanUp();
+    $transaction = $this->connection->startTransaction();
+    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::addRootTransactionEndCallback() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface::addPostTransactionCallback() instead. See https://www.drupal.org/node/3381002');
+    $this->connection->addRootTransactionEndCallback([$this, 'rootTransactionCallback']);
+    $this->insertRow('row');
+    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::commit() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Do not commit the connection, void the Transaction objects instead. See https://www.drupal.org/node/3381002');
+    try {
+      $this->connection->commit();
+    }
+    catch (TransactionExplicitCommitNotAllowedException $e) {
+      // Do nothing.
+    }
+    $transaction = NULL;
+    $this->assertRowPresent('row');
+
+    $this->cleanUp();
+    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::pushTransaction() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002');
+    $this->connection->pushTransaction('foo');
+    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::popTransaction() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002');
+    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::popCommittableTransactions() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002');
+    $this->expectDeprecation('Drupal\\Core\\Database\\Connection::doCommit() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use TransactionManagerInterface methods instead. See https://www.drupal.org/node/3381002');
+    $this->connection->popTransaction('foo');
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php b/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php
index 8704425e2aaebfd76d85eeef982065d88c7a2e6b..e4bc165355dc2d1a2039a4c50e16e8d2ebc6fda6 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/FetchTest.php
@@ -97,14 +97,35 @@ public function testQueryFetchObjectClass() {
     $this->assertSame(1, $records, 'There is only one record.');
   }
 
+  /**
+   * Confirms that we can fetch a record into a class without constructor args.
+   *
+   * @see \Drupal\Tests\system\Functional\Database\FakeRecord
+   * @see \Drupal\Core\Database\StatementPrefetch::fetchObject
+   */
+  public function testQueryFetchObjectClassNoConstructorArgs(): void {
+    $records = 0;
+    $query = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25]);
+    while ($result = $query->fetchObject(FakeRecord::class)) {
+      $records += 1;
+      $this->assertInstanceOf(FakeRecord::class, $result);
+      $this->assertSame('John', $result->name);
+      $this->assertSame(0, $result->fakeArg);
+    }
+    $this->assertSame(1, $records);
+  }
+
   /**
    * Confirms that we can fetch a record into a new instance of a custom class.
    *
    * The name of the class is determined from a value of the first column.
    *
    * @see \Drupal\Tests\system\Functional\Database\FakeRecord
+   *
+   * @group legacy
    */
   public function testQueryFetchClasstype() {
+    $this->expectDeprecation('Fetch mode FETCH_CLASS | FETCH_CLASSTYPE is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999');
     $records = [];
     $result = $this->connection->query('SELECT [classname], [name], [job] FROM {test_classtype} WHERE [age] = :age', [':age' => 26], ['fetch' => \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE]);
     foreach ($result as $record) {
@@ -136,8 +157,11 @@ public function testQueryFetchNum() {
 
   /**
    * Confirms that we can fetch a record into a doubly-keyed array explicitly.
+   *
+   * @group legacy
    */
   public function testQueryFetchBoth() {
+    $this->expectDeprecation('Fetch mode FETCH_BOTH is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999');
     $records = [];
     $result = $this->connection->query('SELECT [name] FROM {test} WHERE [age] = :age', [':age' => 25], ['fetch' => \PDO::FETCH_BOTH]);
     foreach ($result as $record) {
diff --git a/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php b/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
index 11b2da2786051e2225b6e89aec24425a87ed20f3..853de1e2656050ad2d565db88d8ce1896220d6ed 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
@@ -222,4 +222,19 @@ public function testMergeWithReservedWords() {
     $this->assertEquals('2', $person->id);
   }
 
+  /**
+   * Tests deprecation of Merge::key() with array $field argument.
+   *
+   * @group legacy
+   */
+  public function testDeprecatedKeyArrayArgument(): void {
+    $this->expectDeprecation('Passing an array to the $field argument of Drupal\Core\Database\Query\Merge::key() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. See https://www.drupal.org/node/2205327');
+    $this->connection->merge('select')
+      ->key(['id' => 2])
+      ->execute();
+    $person = $this->connection->query('SELECT * FROM {select} WHERE [id] = :id', [':id' => 2])->fetch();
+    $this->assertEquals('', $person->update);
+    $this->assertEquals('2', $person->id);
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/BaseFieldOverrideValidationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/BaseFieldOverrideValidationTest.php
index e13c50d0eeaf2e2c416c900c1e36aeda752b2af8..71caf675a25c0a9a99b8f06d3308b20b4a997dca 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/BaseFieldOverrideValidationTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/BaseFieldOverrideValidationTest.php
@@ -31,4 +31,16 @@ protected function setUp(): void {
     $this->entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function testImmutableProperties(array $valid_values = []): void {
+    // If we don't clear the previous settings here, we will get unrelated
+    // validation errors (in addition to the one we're expecting), because the
+    // settings from the *old* field_type won't match the config schema for the
+    // settings of the *new* field_type.
+    $this->entity->set('settings', []);
+    parent::testImmutableProperties($valid_values);
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
index cd29ff5a7dd88fd0f573ae9758b8801738180a9a..46aa060fc33242f2f61268fde36189fbe80ba6bf 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityHasChangesTest.php
@@ -50,7 +50,7 @@ public function testHasTranslationChanges() {
     ]);
     $user2->save();
 
-    /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->container->get('entity_type.manager')
       ->getStorage('entity_test_mulrev_changed_rev');
     /** @var \Drupal\entity_test\Entity\EntityTestMulRevChangedWithRevisionLog $entity */
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
index 261763b142073cdc78d082dc9b77f77e5cbde3d0..a293e1a2399b73096ac0969bcabdc565ef4d53b6 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDefinitionUpdateTest.php
@@ -25,6 +25,7 @@
  * @coversDefaultClass \Drupal\Core\Entity\EntityDefinitionUpdateManager
  *
  * @group Entity
+ * @group #slow
  */
 class EntityDefinitionUpdateTest extends EntityKernelTestBase {
 
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
index c582efc39bd17e914d9fe1dfd0319d122b0efa08..11c71b209562e884bb31fc1bebe551c5bfa34c2b 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFieldTest.php
@@ -103,6 +103,7 @@ protected function createTestEntity($entity_type) {
    * Test setting field values on revisionable entities.
    */
   public function testFieldEntityRevisionWrite() {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
 
     // Create a new entity, with a field value 'foo'.
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFormModeValidationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFormModeValidationTest.php
index 31e31249bb20336d27b2d279ebeba6c469e711f7..3725ffec67c612caf3deb002b827f36b193d7a36 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFormModeValidationTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFormModeValidationTest.php
@@ -34,4 +34,12 @@ protected function setUp(): void {
     $this->entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function testImmutableProperties(array $valid_values = []): void {
+    $valid_values['id'] = 'user.test_changed';
+    parent::testImmutableProperties($valid_values);
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityRevisionTranslationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityRevisionTranslationTest.php
index d3a5a5ccf8a022becf2b652764a2dd0404547619..ba8ffc5df1203b4bfb37b76e9494ff1626fab425 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityRevisionTranslationTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityRevisionTranslationTest.php
@@ -63,6 +63,7 @@ public function testNewRevisionAfterTranslation() {
    */
   public function testRevertRevisionAfterTranslation() {
     $user = $this->createUser();
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->entityTypeManager->getStorage('entity_test_mulrev');
 
     // Create a test entity.
@@ -98,6 +99,7 @@ public function testRevertRevisionAfterTranslation() {
    */
   public function testTranslationValuesWhenSavingPendingRevisions() {
     $user = $this->createUser();
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->entityTypeManager->getStorage('entity_test_mulrev');
 
     // Create a test entity and a translation for it.
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityViewModeValidationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewModeValidationTest.php
index 011a5e4844cc2275e9a8c6c68dea0234dbfb3a97..53f759121ae60741030bcf06f5b6ba0c343ade22 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityViewModeValidationTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewModeValidationTest.php
@@ -34,4 +34,12 @@ protected function setUp(): void {
     $this->entity->save();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function testImmutableProperties(array $valid_values = []): void {
+    $valid_values['id'] = 'user.test_changed';
+    parent::testImmutableProperties($valid_values);
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/FieldSqlStorageTest.php b/core/tests/Drupal/KernelTests/Core/Entity/FieldSqlStorageTest.php
index 3c938e5db30af8e3e2501c11f06c52115ac56649..1b15ca5cba27c388a3967f1f9be0fabaa02adab9 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/FieldSqlStorageTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/FieldSqlStorageTest.php
@@ -107,6 +107,7 @@ protected function setUp(): void {
    */
   public function testFieldLoad() {
     $entity_type = $bundle = 'entity_test_rev';
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->container->get('entity_type.manager')->getStorage($entity_type);
 
     $columns = ['bundle', 'deleted', 'entity_id', 'revision_id', 'delta', 'langcode', $this->tableMapping->getFieldColumnName($this->fieldStorage, 'value')];
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ImmutablePropertiesConstraintValidatorTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ImmutablePropertiesConstraintValidatorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..57cfe31544758d4898d0f01a89b40cf3d513ee9a
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Entity/ImmutablePropertiesConstraintValidatorTest.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Entity;
+
+use Drupal\block_content\Entity\BlockContentType;
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\TypedDataManagerInterface;
+use Drupal\KernelTests\KernelTestBase;
+use Symfony\Component\Validator\Exception\LogicException;
+use Symfony\Component\Validator\Exception\UnexpectedValueException;
+
+/**
+ * @group Entity
+ * @group Validation
+ *
+ * @covers \Drupal\Core\Entity\Plugin\Validation\Constraint\ImmutablePropertiesConstraint
+ * @covers \Drupal\Core\Entity\Plugin\Validation\Constraint\ImmutablePropertiesConstraintValidator
+ */
+class ImmutablePropertiesConstraintValidatorTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['block_content'];
+
+  /**
+   * Tests that only config entities are accepted by the validator.
+   */
+  public function testValidatorRequiresAConfigEntity(): void {
+    $definition = DataDefinition::createFromDataType('any')
+      ->addConstraint('ImmutableProperties', ['read_only']);
+    $data = $this->container->get(TypedDataManagerInterface::class)
+      ->create($definition, 39);
+    $this->expectException(UnexpectedValueException::class);
+    $this->expectExceptionMessage('Expected argument of type "' . ConfigEntityInterface::class . '", "int" given');
+    $data->validate();
+  }
+
+  /**
+   * Tests that the validator throws an exception for non-existent properties.
+   */
+  public function testValidatorRejectsANonExistentProperty(): void {
+    /** @var \Drupal\block_content\BlockContentTypeInterface $entity */
+    $entity = BlockContentType::create([
+      'id' => 'test',
+      'label' => 'Test',
+    ]);
+    $entity->save();
+    $this->assertFalse(property_exists($entity, 'non_existent'));
+
+    $definition = DataDefinition::createFromDataType('entity:block_content_type')
+      ->addConstraint('ImmutableProperties', ['non_existent']);
+
+    $this->expectException(LogicException::class);
+    $this->expectExceptionMessage("The entity does not have a 'non_existent' property.");
+    $violations = $this->container->get(TypedDataManagerInterface::class)
+      ->create($definition, $entity)
+      ->validate();
+  }
+
+  /**
+   * Tests that entities without an ID will raise an exception.
+   */
+  public function testValidatedEntityMustHaveAnId(): void {
+    $entity = $this->prophesize(ConfigEntityInterface::class);
+    $entity->isNew()->willReturn(FALSE)->shouldBeCalled();
+    $entity->getOriginalId()->shouldBeCalled();
+    $entity->id()->shouldBeCalled();
+
+    $definition = DataDefinition::createFromDataType('any')
+      ->addConstraint('ImmutableProperties', ['read_only']);
+    $data = $this->container->get(TypedDataManagerInterface::class)
+      ->create($definition, $entity->reveal());
+    $this->expectException(LogicException::class);
+    $this->expectExceptionMessage('The entity does not have an ID.');
+    $data->validate();
+  }
+
+  /**
+   * Tests that changing a config entity's immutable property raises an error.
+   */
+  public function testImmutablePropertyCannotBeChanged(): void {
+    /** @var \Drupal\block_content\BlockContentTypeInterface $entity */
+    $entity = BlockContentType::create([
+      'id' => 'test',
+      'label' => 'Test',
+    ]);
+    $entity->save();
+
+    $definition = DataDefinition::createFromDataType('entity:block_content_type')
+      ->addConstraint('ImmutableProperties', ['id', 'description']);
+
+    /** @var \Drupal\Core\TypedData\TypedDataManagerInterface $typed_data_manager */
+    $typed_data_manager = $this->container->get(TypedDataManagerInterface::class);
+
+    // Try changing one immutable property, and one mutable property.
+    $entity->set('id', 'foo')->set('label', 'Testing!');
+    $violations = $typed_data_manager->create($definition, $entity)->validate();
+    $this->assertCount(1, $violations);
+    $this->assertSame("The 'id' property cannot be changed.", (string) $violations[0]->getMessage());
+
+    // Ensure we get multiple violations if more than one immutable property is
+    // changed.
+    $entity->set('description', "From hell's heart, I describe thee!");
+    $violations = $typed_data_manager->create($definition, $entity)->validate();
+    $this->assertCount(2, $violations);
+    $this->assertSame("The 'id' property cannot be changed.", (string) $violations[0]->getMessage());
+    $this->assertSame("The 'description' property cannot be changed.", (string) $violations[1]->getMessage());
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/RevisionRouteProviderTest.php b/core/tests/Drupal/KernelTests/Core/Entity/RevisionRouteProviderTest.php
index bdde7ca46adb2072d834cbfaa46ed5a695c888ff..59cd473b6c4810359ddb230e011a0b0c73e7c12f 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/RevisionRouteProviderTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/RevisionRouteProviderTest.php
@@ -66,6 +66,7 @@ public function testOperationAccessOverview(): void {
    * @dataProvider providerOperationAccessRevisionRoutes
    */
   public function testOperationAccessRevisionRoutes(string $linkTemplate, string $entityLabel): void {
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $entityStorage */
     $entityStorage = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
 
     $entity = EntityTestRev::create()
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/RevisionableContentEntityBaseTest.php b/core/tests/Drupal/KernelTests/Core/Entity/RevisionableContentEntityBaseTest.php
index 15d56e68d604b009873ded5795d9f5e9ac3051b8..b0ce3373d0b0d3a6818e7c771752ef0ca6b76e9d 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/RevisionableContentEntityBaseTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/RevisionableContentEntityBaseTest.php
@@ -59,6 +59,7 @@ public function testRevisionableContentEntity() {
     $revision_id = $entity->getRevisionId();
     $revision_ids[] = $revision_id;
 
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = \Drupal::entityTypeManager()->getStorage('entity_test_mul_revlog');
     $entity = $storage->loadRevision($revision_id);
     $this->assertEquals($random_timestamp, $entity->getRevisionCreationTime());
@@ -135,6 +136,7 @@ public function testWasDefaultRevision() {
     $this->assertFalse($entity->wasDefaultRevision());
 
     // Check that the default revision status was stored correctly.
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
     $storage = $this->entityTypeManager->getStorage($entity_type_id);
     foreach ([TRUE, FALSE, TRUE, FALSE] as $index => $expected) {
       /** @var \Drupal\entity_test_revlog\Entity\EntityTestMulWithRevisionLog $revision */
diff --git a/core/tests/Drupal/KernelTests/Core/Field/FieldSettingsTest.php b/core/tests/Drupal/KernelTests/Core/Field/FieldSettingsTest.php
index 545de980983cef71303b29be9134df081c45de92..996b6c2355fa99c620a0ab68e3c8a1aadb2cdfd9 100644
--- a/core/tests/Drupal/KernelTests/Core/Field/FieldSettingsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Field/FieldSettingsTest.php
@@ -113,14 +113,19 @@ public function testConfigurableFieldSettings() {
       'type' => 'test_field',
     ]);
     $field_storage->save();
+    $expected_settings = [
+      'test_field_storage_setting' => 'dummy test string',
+      'changeable' => 'a changeable field storage setting',
+      'unchangeable' => 'an unchangeable field storage setting',
+      'translatable_storage_setting' => 'a translatable field storage setting',
+      'storage_setting_from_config_data' => 'TRUE',
+    ];
+    $this->assertEquals($expected_settings, $field_storage->getSettings());
+
     $field = FieldConfig::create([
       'field_storage' => $field_storage,
       'bundle' => 'entity_test',
     ]);
-    // Note: FieldConfig does not populate default settings until the config
-    // is saved.
-    // @todo Remove once https://www.drupal.org/node/2327883 is fixed.
-    $field->save();
 
     // Check that the default settings have been populated. Note: getSettings()
     // returns both storage and field settings.
@@ -131,7 +136,6 @@ public function testConfigurableFieldSettings() {
       'translatable_storage_setting' => 'a translatable field storage setting',
       'test_field_setting' => 'dummy test string',
       'translatable_field_setting' => 'a translatable field setting',
-      'field_setting_from_config_data' => 'TRUE',
       'storage_setting_from_config_data' => 'TRUE',
     ];
     $this->assertEquals($expected_settings, $field->getSettings());
@@ -141,6 +145,20 @@ public function testConfigurableFieldSettings() {
     $expected_settings['test_field_setting'] = 'another test string';
     $field->setSettings(['test_field_setting' => $expected_settings['test_field_setting']]);
     $this->assertEquals($expected_settings, $field->getSettings());
+
+    // Save the field and check the expected settings.
+    $field->save();
+
+    $expected_settings['field_setting_from_config_data'] = 'TRUE';
+    $this->assertEquals($expected_settings, $field->getSettings());
+
+    $field = FieldConfig::loadByName('entity_test', 'entity_test', 'test_field');
+
+    $this->assertEquals($expected_settings, $field->getSettings());
+
+    $expected_settings['test_field_setting'] = 'yet another test string';
+    $field->setSettings(['test_field_setting' => $expected_settings['test_field_setting']]);
+    $this->assertEquals($expected_settings, $field->getSettings());
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Image/ToolkitGdTest.php b/core/tests/Drupal/KernelTests/Core/Image/ToolkitGdTest.php
index 817e4e2698a358514fc1f89995cabe5487b54df4..abc25ab40ba6b913ca4521772568732895a7d921 100644
--- a/core/tests/Drupal/KernelTests/Core/Image/ToolkitGdTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Image/ToolkitGdTest.php
@@ -12,6 +12,7 @@
  *
  * @coversDefaultClass \Drupal\system\Plugin\ImageToolkit\GDToolkit
  * @group Image
+ * @group #slow
  * @requires extension gd
  */
 class ToolkitGdTest extends KernelTestBase {
@@ -544,16 +545,16 @@ public function testResourceDeprecation() {
     $image = imagecreate(10, 10);
     $this->expectDeprecation('Drupal\system\Plugin\ImageToolkit\GDToolkit::setResource() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::setImage() instead. See https://www.drupal.org/node/3265963');
     $toolkit->setResource($image);
-    $this->expectDeprecation('Checking the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead.');
+    $this->expectDeprecation('Checking the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead. See https://www.drupal.org/node/3265963');
     $this->assertTrue(isset($toolkit->resource));
     $this->expectDeprecation('Drupal\system\Plugin\ImageToolkit\GDToolkit::getResource() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::getImage() instead. See https://www.drupal.org/node/3265963');
     $this->assertSame($image, $toolkit->getResource());
-    $this->expectDeprecation('Accessing the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead.');
+    $this->expectDeprecation('Accessing the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead. See https://www.drupal.org/node/3265963');
     $this->assertSame($image, $toolkit->resource);
-    $this->expectDeprecation('Setting the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead.');
+    $this->expectDeprecation('Setting the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead. See https://www.drupal.org/node/3265963');
     $toolkit->resource = NULL;
     $this->assertNull($toolkit->getImage());
-    $this->expectDeprecation('Unsetting the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead.');
+    $this->expectDeprecation('Unsetting the \Drupal\system\Plugin\ImageToolkit\GDToolkit::resource property is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use \Drupal\system\Plugin\ImageToolkit\GDToolkit::image instead. See https://www.drupal.org/node/3265963');
     $toolkit->setImage($image);
     unset($toolkit->resource);
     $this->assertNull($toolkit->getImage());
diff --git a/core/tests/Drupal/Nightwatch/Commands/drupalInstallModule.js b/core/tests/Drupal/Nightwatch/Commands/drupalInstallModule.js
index c791774974e5229803b67751c44d67e1b37d22cb..be8b1aa0b02931da6673ca6ed33c26ed78a22c39 100644
--- a/core/tests/Drupal/Nightwatch/Commands/drupalInstallModule.js
+++ b/core/tests/Drupal/Nightwatch/Commands/drupalInstallModule.js
@@ -3,12 +3,14 @@
  *
  * @param {string} module
  *   The module machine name to enable.
+ * @param {boolean} force
+ *   Force to install dependencies if applicable.
  * @param {function} callback
  *   A callback which will be called, when the module has been enabled.
  * @return {object}
  *   The drupalInstallModule command.
  */
-exports.command = function drupalInstallModule(module, callback) {
+exports.command = function drupalInstallModule(module, force, callback) {
   const self = this;
   this.drupalLoginAsAdmin(() => {
     this.drupalRelativeURL('/admin/modules')
@@ -22,13 +24,22 @@ exports.command = function drupalInstallModule(module, callback) {
         10000,
       )
       .click(`form.system-modules [name="modules[${module}][enable]"]`)
-      .submitForm('form.system-modules')
-      // Wait for the checkbox for the module to be disabled as a sign that the
-      // module has been enabled.
-      .waitForElementPresent(
-        `form.system-modules [name="modules[${module}][enable]"]:disabled`,
+      .submitForm('form.system-modules');
+    if (force) {
+      // Click `Continue` if applicable.
+      this.waitForElementPresent(
+        '#system-modules-confirm-form',
         10000,
+        false,
+        () => self.click('input[value=Continue]'),
       );
+    }
+    // Wait for the checkbox for the module to be disabled as a sign that the
+    // module has been enabled.
+    this.waitForElementPresent(
+      `form.system-modules [name="modules[${module}][enable]"]:disabled`,
+      10000,
+    );
   }).perform(() => {
     if (typeof callback === 'function') {
       callback.call(self);
diff --git a/core/tests/Drupal/Nightwatch/Tests/ajaxExecutionOrderTest.js b/core/tests/Drupal/Nightwatch/Tests/ajaxExecutionOrderTest.js
index da54f6015a6c5a52527433f761f58d498ca3fd4e..6284e99054231da823094cc7baf34861714774f2 100644
--- a/core/tests/Drupal/Nightwatch/Tests/ajaxExecutionOrderTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/ajaxExecutionOrderTest.js
@@ -1,20 +1,7 @@
 module.exports = {
   '@tags': ['core', 'ajax'],
   before(browser) {
-    browser.drupalInstall().drupalLoginAsAdmin(() => {
-      browser
-        .drupalRelativeURL('/admin/modules')
-        .setValue('input[type="search"]', 'Ajax test')
-        .waitForElementVisible('input[name="modules[ajax_test][enable]"]', 1000)
-        .click('input[name="modules[ajax_test][enable]"]')
-        .submitForm('input[type="submit"]') // Submit module form.
-        .waitForElementVisible(
-          '.system-modules-confirm-form input[value="Continue"]',
-          2000,
-        )
-        .submitForm('input[value="Continue"]') // Confirm installation of dependencies.
-        .waitForElementVisible('.system-modules', 10000);
-    });
+    browser.drupalInstall().drupalInstallModule('ajax_test', true);
   },
   after(browser) {
     browser.drupalUninstall();
diff --git a/core/tests/Drupal/Nightwatch/Tests/claroAutocompleteTest.js b/core/tests/Drupal/Nightwatch/Tests/claroAutocompleteTest.js
index 054f40ab7937ab993d8bf06d603a56e55b9aa9e6..446b52665d8675dfe2a00058be16e3b99dd86d7d 100644
--- a/core/tests/Drupal/Nightwatch/Tests/claroAutocompleteTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/claroAutocompleteTest.js
@@ -2,25 +2,15 @@ module.exports = {
   '@tags': ['core'],
 
   before(browser) {
-    browser.drupalInstall().drupalLoginAsAdmin(() => {
-      browser
-        .drupalRelativeURL('/admin/modules')
-        .setValue('input[type="search"]', 'FormAPI Test')
-        .waitForElementVisible('input[name="modules[form_test][enable]"]', 1000)
-        .click('input[name="modules[form_test][enable]"]')
-        .submitForm('input[type="submit"]') // Submit module form.
-        .waitForElementVisible(
-          '.system-modules-confirm-form input[value="Continue"]',
-          2000,
-        )
-        .submitForm('input[value="Continue"]') // Confirm installation of dependencies.
-        .waitForElementVisible('.system-modules', 10000);
-
-      browser
-        .drupalRelativeURL('/admin/appearance')
-        .click('[title="Install Claro as default theme"]')
-        .waitForElementVisible('.system-themes-list', 10000); // Confirm installation.
-    });
+    browser
+      .drupalInstall()
+      .drupalInstallModule('form_test', true)
+      .drupalLoginAsAdmin(() => {
+        browser
+          .drupalRelativeURL('/admin/appearance')
+          .click('[title="Install Claro as default theme"]')
+          .waitForElementVisible('.system-themes-list', 10000); // Confirm installation.
+      });
   },
   after(browser) {
     browser.drupalUninstall();
diff --git a/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js b/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js
index 7c82cc83c6b08cec3a10a5375bacb003038f10cb..0cbcf58687887010490a77f55f4fa223c92f4a88 100644
--- a/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js
@@ -758,17 +758,7 @@ testScenarios.element = testScenarios.selector;
 module.exports = {
   '@tags': ['core'],
   before(browser) {
-    browser.drupalInstall().drupalLoginAsAdmin(() => {
-      browser
-        .drupalRelativeURL('/admin/modules')
-        .setValue('input[type="search"]', 'position Shim Test')
-        .waitForElementVisible(
-          'input[name="modules[position_shim_test][enable]"]',
-          1000,
-        )
-        .click('input[name="modules[position_shim_test][enable]"]')
-        .click('input[type="submit"]');
-    });
+    browser.drupalInstall().drupalInstallModule('position_shim_test');
   },
   after(browser) {
     browser.drupalUninstall();
diff --git a/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js b/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js
index c3cb964306530481c1f8b390be0fb77a5624ffc5..b146cd290c47cff0b5f576ac3f3d4f9a14a56903 100644
--- a/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js
@@ -1,17 +1,7 @@
 module.exports = {
   '@tags': ['core'],
   before(browser) {
-    browser.drupalInstall().drupalLoginAsAdmin(() => {
-      browser
-        .drupalRelativeURL('/admin/modules')
-        .setValue('input[type="search"]', 'JS Deprecation test')
-        .waitForElementVisible(
-          'input[name="modules[js_deprecation_test][enable]"]',
-          1000,
-        )
-        .click('input[name="modules[js_deprecation_test][enable]"]')
-        .click('input[type="submit"]'); // Submit module form.
-    });
+    browser.drupalInstall().drupalInstallModule('js_deprecation_test');
   },
   after(browser) {
     browser.drupalUninstall();
diff --git a/core/tests/Drupal/Nightwatch/Tests/jsOnceTest.js b/core/tests/Drupal/Nightwatch/Tests/jsOnceTest.js
index 9e8a7476f3074de51cd67695c9dd83edddd7716d..3a34078520487cc7748ba7ccef19065825b37b59 100644
--- a/core/tests/Drupal/Nightwatch/Tests/jsOnceTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/jsOnceTest.js
@@ -1,17 +1,7 @@
 module.exports = {
   '@tags': ['core'],
   before(browser) {
-    browser.drupalInstall().drupalLoginAsAdmin(() => {
-      browser
-        .drupalRelativeURL('/admin/modules')
-        .setValue('input[type="search"]', 'JS Once Test')
-        .waitForElementVisible(
-          'input[name="modules[js_once_test][enable]"]',
-          1000,
-        )
-        .click('input[name="modules[js_once_test][enable]"]')
-        .click('input[type="submit"]'); // Submit module form.
-    });
+    browser.drupalInstall().drupalInstallModule('js_once_test');
   },
   after(browser) {
     browser.drupalUninstall();
diff --git a/core/tests/Drupal/Nightwatch/Tests/loginTest.js b/core/tests/Drupal/Nightwatch/Tests/loginTest.js
index 3eb398f8a9370b60c58c00dc9c96f5ee09427945..500ed7846c61f0569d6eac3a20e8e62ad5442239 100644
--- a/core/tests/Drupal/Nightwatch/Tests/loginTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/loginTest.js
@@ -13,7 +13,7 @@ module.exports = {
       .drupalCreateUser({
         name: 'user',
         password: '123',
-        permissions: ['access site reports'],
+        permissions: ['access site reports', 'administer site configuration'],
       })
       .drupalLogin({ name: 'user', password: '123' })
       .drupalRelativeURL('/admin/reports')
diff --git a/core/tests/Drupal/Nightwatch/Tests/statesTest.js b/core/tests/Drupal/Nightwatch/Tests/statesTest.js
index 4d48d8d3aac8ae31d02643187fc64d3f50777d7c..8c037e53eccc95cb638430686c4f0a56bb5816ce 100644
--- a/core/tests/Drupal/Nightwatch/Tests/statesTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/statesTest.js
@@ -1,15 +1,7 @@
 module.exports = {
   '@tags': ['core'],
   before(browser) {
-    browser.drupalInstall().drupalLoginAsAdmin(() => {
-      browser
-        .drupalRelativeURL('/admin/modules')
-        .setValue('input[type="search"]', 'FormAPI')
-        .waitForElementVisible('input[name="modules[form_test][enable]"]', 1000)
-        .click('input[name="modules[form_test][enable]"]')
-        .click('input[type="submit"]') // Submit module form.
-        .click('input[type="submit"]'); // Confirm installation of dependencies.
-    });
+    browser.drupalInstall().drupalInstallModule('form_test', true);
   },
   after(browser) {
     browser.drupalUninstall();
diff --git a/core/tests/Drupal/Nightwatch/Tests/tabbableShimTest.js b/core/tests/Drupal/Nightwatch/Tests/tabbableShimTest.js
index f1a87ad09c569da34f3d4cd2cb60ea6806dcbf46..c76026d6ff899313db47175f53b6acdb55b41586 100644
--- a/core/tests/Drupal/Nightwatch/Tests/tabbableShimTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/tabbableShimTest.js
@@ -258,17 +258,7 @@ const dialogIntegrationTestScenarios = [
 module.exports = {
   '@tags': ['core'],
   before(browser) {
-    browser.drupalInstall().drupalLoginAsAdmin(() => {
-      browser
-        .drupalRelativeURL('/admin/modules')
-        .setValue('input[type="search"]', 'Tabbable Shim Test')
-        .waitForElementVisible(
-          'input[name="modules[tabbable_shim_test][enable]"]',
-          1000,
-        )
-        .click('input[name="modules[tabbable_shim_test][enable]"]')
-        .click('input[type="submit"]');
-    });
+    browser.drupalInstall().drupalInstallModule('tabbable_shim_test');
   },
   after(browser) {
     browser.drupalUninstall();
diff --git a/core/tests/Drupal/Nightwatch/Tests/tabbingManagerTest.js b/core/tests/Drupal/Nightwatch/Tests/tabbingManagerTest.js
index bee2da6f0c90b081b1fb967b338eaf0f2082e9e2..a90cd7bf9f8e5f7f73e17296e3f5bf81e7e1ac38 100644
--- a/core/tests/Drupal/Nightwatch/Tests/tabbingManagerTest.js
+++ b/core/tests/Drupal/Nightwatch/Tests/tabbingManagerTest.js
@@ -1,17 +1,7 @@
 module.exports = {
   '@tags': ['core'],
   before(browser) {
-    browser.drupalInstall().drupalLoginAsAdmin(() => {
-      browser
-        .drupalRelativeURL('/admin/modules')
-        .setValue('input[type="search"]', 'Tabbing Manager Test')
-        .waitForElementVisible(
-          'input[name="modules[tabbingmanager_test][enable]"]',
-          1000,
-        )
-        .click('input[name="modules[tabbingmanager_test][enable]"]')
-        .click('input[type="submit"]');
-    });
+    browser.drupalInstall().drupalInstallModule('tabbingmanager_test');
   },
   after(browser) {
     browser.drupalUninstall();
diff --git a/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
index 98dc0e6b53d836733501aa4409a048eecb5754cd..cf6016c7e5c67d911f0e7937d70522fcb069737e 100644
--- a/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
+++ b/core/tests/Drupal/Tests/Component/Datetime/TimeTest.php
@@ -83,7 +83,9 @@ public function testGetRequestMicroTime() {
    * @covers ::getRequestTime
    */
   public function testGetRequestTimeNoRequest() {
-    $expected = 12345678;
+    // With no request, and no global variable, we expect to get the int part
+    // of the microtime.
+    $expected = 1234567;
     unset($_SERVER['REQUEST_TIME']);
     $this->assertEquals($expected, $this->time->getRequestTime());
     $_SERVER['REQUEST_TIME'] = 23456789;
diff --git a/core/tests/Drupal/Tests/Component/Datetime/TimeWithNoRequestTest.php b/core/tests/Drupal/Tests/Component/Datetime/TimeWithNoRequestTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c66e1bb440076a4d7ea6414cc41bdad362f9a459
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Datetime/TimeWithNoRequestTest.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\Tests\Component\Datetime;
+
+use Drupal\Component\Datetime\Time;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Tests that getRequest(Micro)Time works when no underlying request exists.
+ *
+ * @coversDefaultClass \Drupal\Component\Datetime\Time
+ * @group Datetime
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+class TimeWithNoRequestTest extends TestCase {
+
+  /**
+   * The time class for testing.
+   */
+  protected Time $time;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    // We need to explicitly unset the $_SERVER variables, so that Time is
+    // forced to look for current time.
+    unset($_SERVER['REQUEST_TIME']);
+    unset($_SERVER['REQUEST_TIME_FLOAT']);
+
+    $this->time = new Time();
+  }
+
+  /**
+   * Tests the getRequestTime method.
+   *
+   * @covers ::getRequestTime
+   */
+  public function testGetRequestTimeImmutable(): void {
+    $requestTime = $this->time->getRequestTime();
+    sleep(2);
+    $this->assertSame($requestTime, $this->time->getRequestTime());
+  }
+
+  /**
+   * Tests the getRequestMicroTime method.
+   *
+   * @covers ::getRequestMicroTime
+   */
+  public function testGetRequestMicroTimeImmutable() {
+    $requestTime = $this->time->getRequestMicroTime();
+    usleep(20000);
+    $this->assertSame($requestTime, $this->time->getRequestMicroTime());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Diff/DiffOpOutputBuilderTest.php b/core/tests/Drupal/Tests/Component/Diff/DiffOpOutputBuilderTest.php
index bb0f70de2e19927ba6b8a347dbf151fe6a08689d..53ada909fec2e4ff45b159a390c53e893b9589a6 100644
--- a/core/tests/Drupal/Tests/Component/Diff/DiffOpOutputBuilderTest.php
+++ b/core/tests/Drupal/Tests/Component/Diff/DiffOpOutputBuilderTest.php
@@ -65,6 +65,24 @@ public function provideTestDiff(): array {
         ['a', 'b', 'd'],
         ['a'],
       ],
+      'change-copy' => [
+        [
+          new DiffOpChange(['aa', 'bb', 'cc'], ['a', 'c']),
+          new DiffOpCopy(['d']),
+        ],
+        ['aa', 'bb', 'cc', 'd'],
+        ['a', 'c', 'd'],
+      ],
+      'copy-change-copy-change' => [
+        [
+          new DiffOpCopy(['a']),
+          new DiffOpChange(['bb'], ['b', 'c']),
+          new DiffOpCopy(['d']),
+          new DiffOpChange(['ee'], ['e']),
+        ],
+        ['a', 'bb', 'd', 'ee'],
+        ['a', 'b', 'c', 'd', 'e'],
+      ],
     ];
   }
 
@@ -103,12 +121,11 @@ public function testDiffInfiniteLoop(): void {
     $differ = new Differ($diffOpBuilder);
     $diff = $differ->diffToArray($from, $to);
     $diffOps = $diffOpBuilder->toOpsArray($diff);
-    $this->assertCount(5, $diffOps);
+    $this->assertCount(4, $diffOps);
     $this->assertEquals($diffOps[0], new DiffOpAdd(['    - image.style.max_325x325']));
     $this->assertEquals($diffOps[1], new DiffOpCopy(['    - image.style.max_650x650']));
-    $this->assertEquals($diffOps[2], new DiffOpChange(['    - image.style.max_325x325'], ['_core:']));
-    $this->assertEquals($diffOps[3], new DiffOpAdd(['  default_config_hash: 3mjM9p-kQ8syzH7N8T0L9OnCJDSPvHAZoi3q6jcXJKM']));
-    $this->assertEquals($diffOps[4], new DiffOpCopy(['fallback_image_style: max_325x325', '']));
+    $this->assertEquals($diffOps[2], new DiffOpChange(['    - image.style.max_325x325'], ['_core:', '  default_config_hash: 3mjM9p-kQ8syzH7N8T0L9OnCJDSPvHAZoi3q6jcXJKM']));
+    $this->assertEquals($diffOps[3], new DiffOpCopy(['fallback_image_style: max_325x325', '']));
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Component/Diff/Engine/DiffEngineTest.php b/core/tests/Drupal/Tests/Component/Diff/Engine/DiffEngineTest.php
index 1020373e6ac69ad8f57c64f6025baa7061b2adc5..70265df1f6a557c0386dbdaed8af34ad766c3f2a 100644
--- a/core/tests/Drupal/Tests/Component/Diff/Engine/DiffEngineTest.php
+++ b/core/tests/Drupal/Tests/Component/Diff/Engine/DiffEngineTest.php
@@ -70,6 +70,24 @@ public function provideTestDiff() {
         ['a', 'b', 'd'],
         ['a'],
       ],
+      'change-copy' => [
+        [
+          DiffOpChange::class,
+          DiffOpCopy::class,
+        ],
+        ['aa', 'bb', 'cc', 'd'],
+        ['a', 'c', 'd'],
+      ],
+      'copy-change-copy-change' => [
+        [
+          DiffOpCopy::class,
+          DiffOpChange::class,
+          DiffOpCopy::class,
+          DiffOpChange::class,
+        ],
+        ['a', 'bb', 'd', 'ee'],
+        ['a', 'b', 'c', 'd', 'e'],
+      ],
     ];
   }
 
diff --git a/core/tests/Drupal/Tests/Component/Plugin/PluginHelperLegacyTest.php b/core/tests/Drupal/Tests/Component/Plugin/PluginHelperLegacyTest.php
index bed7ab2d96065dd270711f24e89efc0c2b350e4d..5dc3c674215ba34deeb76a7595cf3b0cc91ff58b 100644
--- a/core/tests/Drupal/Tests/Component/Plugin/PluginHelperLegacyTest.php
+++ b/core/tests/Drupal/Tests/Component/Plugin/PluginHelperLegacyTest.php
@@ -16,7 +16,7 @@ class PluginHelperLegacyTest extends TestCase {
   use ExpectDeprecationTrait;
 
   public function testPluginHelperDeprecation(): void {
-    $this->expectDeprecation('The Drupal\Component\Plugin\PluginHelper is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, use instanceof() to check for \Drupal\Component\Plugin\ConfigurableInterface. See http://drupal.org/node/3198285');
+    $this->expectDeprecation('The Drupal\Component\Plugin\PluginHelper is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, use instanceof() to check for \Drupal\Component\Plugin\ConfigurableInterface. See https://www.drupal.org/node/3198285');
     $this->assertEquals($this instanceof ConfigurableInterface, PluginHelper::isConfigurable($this));
     $plugin = $this->createMock(ConfigurableInterface::class);
     $this->assertEquals($plugin instanceof ConfigurableInterface, PluginHelper::isConfigurable($plugin));
diff --git a/core/tests/Drupal/Tests/Component/Render/FormattableMarkupTest.php b/core/tests/Drupal/Tests/Component/Render/FormattableMarkupTest.php
index 9cd80b1982d2b6894a47311722982a8eb3f31871..6f651496024d9170e2c9e518b823b025224bffa5 100644
--- a/core/tests/Drupal/Tests/Component/Render/FormattableMarkupTest.php
+++ b/core/tests/Drupal/Tests/Component/Render/FormattableMarkupTest.php
@@ -117,12 +117,12 @@ public function testUnexpectedPlaceholder($string, $arguments, $error_number, $e
    */
   public function providerTestUnexpectedPlaceholder() {
     return [
-      ['Non alpha starting character: ~placeholder', ['~placeholder' => 'replaced'], E_USER_WARNING, 'Invalid placeholder (~placeholder) with string: "Non alpha starting character: ~placeholder"'],
-      ['Alpha starting character: placeholder', ['placeholder' => 'replaced'], E_USER_WARNING, 'Invalid placeholder (placeholder) with string: "Alpha starting character: placeholder"'],
+      ['Non alpha, non-allowed starting character: ~placeholder', ['~placeholder' => 'replaced'], E_USER_WARNING, 'Invalid placeholder (~placeholder) with string: "Non alpha, non-allowed starting character: ~placeholder"'],
+      ['Alpha starting character: placeholder', ['placeholder' => 'replaced'], NULL, ''],
       // Ensure that where the placeholder is located in the string is
       // irrelevant.
-      ['placeholder', ['placeholder' => 'replaced'], E_USER_WARNING, 'Invalid placeholder (placeholder) with string: "placeholder"'],
-      ['No replacements', ['foo' => 'bar'], E_USER_WARNING, 'Invalid placeholder (foo) with string: "No replacements"'],
+      ['placeholder', ['placeholder' => 'replaced'], NULL, ''],
+      ['No replacements', ['foo' => 'bar'], NULL, ''],
     ];
   }
 
diff --git a/core/tests/Drupal/Tests/Component/Render/HtmlEscapedTextTest.php b/core/tests/Drupal/Tests/Component/Render/HtmlEscapedTextTest.php
index 34a365f00861eaf03b6f0539e03c69aa3292b3d6..26fd085e3852cdfa6040350f54e0ca567c1e48be 100644
--- a/core/tests/Drupal/Tests/Component/Render/HtmlEscapedTextTest.php
+++ b/core/tests/Drupal/Tests/Component/Render/HtmlEscapedTextTest.php
@@ -47,10 +47,10 @@ public static function providerToString() {
     $tests[] = [$script_tag, '&lt;script&gt;', 'Escapes &lt;script&gt; even inside an object that implements MarkupInterface.'];
     $tests[] = ["<script>", '&lt;script&gt;', 'Escapes &lt;script&gt;'];
     $tests[] = ['<>&"\'', '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters.'];
-    $specialchars = $prophet->prophesize(MarkupInterface::class);
-    $specialchars->__toString()->willReturn('<>&"\'');
-    $specialchars = $specialchars->reveal();
-    $tests[] = [$specialchars, '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters even inside an object that implements MarkupInterface.'];
+    $special_chars = $prophet->prophesize(MarkupInterface::class);
+    $special_chars->__toString()->willReturn('<>&"\'');
+    $special_chars = $special_chars->reveal();
+    $tests[] = [$special_chars, '&lt;&gt;&amp;&quot;&#039;', 'Escapes reserved HTML characters even inside an object that implements MarkupInterface.'];
 
     return $tests;
   }
diff --git a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
index 96f441681d7c260aeb327adbe3cdce0c298cccdb..b2867ec012dd93efebd27e78b6d11c02a18de86c 100644
--- a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
@@ -300,6 +300,7 @@ public function providerEscape() {
       ['→', '→'],
       ['➼', '➼'],
       ['€', '€'],
+      // cspell:disable-next-line
       ['Drup�al', "Drup\x80al"],
     ];
   }
@@ -391,7 +392,7 @@ public function providerTestTransformRootRelativeUrlsToAbsolute() {
 
     // Double-character carriage return should be normalized.
     $data['line break with double special character'] = ["Test without links but with\r\nsome special characters", 'http://example.com', "Test without links but with\nsome special characters"];
-    $data['line break with single special character'] = ["Test without links but with&#13;\nsome special characters", 'http://example.com', FALSE];
+    $data['line break with single special character'] = ["Test without links but with&#13;\nsome special characters", 'http://example.com', "Test without links but with\nsome special characters"];
     $data['carriage return within html'] = ["<a\rhref='/node'>My link</a>", 'http://example.com', '<a href="http://example.com/node">My link</a>'];
 
     return $data;
diff --git a/core/tests/Drupal/Tests/Component/Utility/XssTest.php b/core/tests/Drupal/Tests/Component/Utility/XssTest.php
index 5182a44b84fe59b39687523a992bb910fa76668b..e076e977b392a48eebe2fba8339acd2d542e6c27 100644
--- a/core/tests/Drupal/Tests/Component/Utility/XssTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/XssTest.php
@@ -7,9 +7,9 @@
 use Drupal\Component\Utility\Xss;
 use PHPUnit\Framework\TestCase;
 
-// cspell:ignore ascript barbaz ckers cript CVEs dynsrc fooÿñ metacharacters
-// cspell:ignore msgbox ncript nfocus nmedi nosuchscheme nosuchtag onmediaerror
-// cspell:ignore scrscriptipt tascript vbscript
+// cspell:ignore ascript barbaz ckers cript CVEs dynsrc fooÿñ msgbox ncript
+// cspell:ignore nfocus nmedi nosuchscheme nosuchtag onmediaerror scrscriptipt
+// cspell:ignore tascript vbscript
 
 /**
  * XSS Filtering tests.
@@ -607,6 +607,16 @@ public function providerTestFilterXssAdminNotNormalized() {
     ];
   }
 
+  /**
+   * Checks that escaped HTML embedded in an attribute is not filtered.
+   *
+   * @see \Drupal\Component\Utility\HtmlSerializerRules
+   */
+  public function testFilterNormalizedHtml5() {
+    $input = '<span data-caption="foo &lt;em&gt;bar&lt;/em&gt;"></span>';
+    $this->assertEquals($input, Xss::filter(Html::normalize($input), ['span']));
+  }
+
   /**
    * Asserts that a text transformed to lowercase with HTML entities decoded does contain a given string.
    *
diff --git a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
index fd2d53100a71593b6d06192af75dc102e0380df1..5c52358e08be0190dde03079ee3c6f1eae2135fd 100644
--- a/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/CustomAccessCheckTest.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Utility\CallableResolver;
 use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -60,12 +61,13 @@ protected function setUp(): void {
   public function testAccess() {
     $route_match = $this->createMock('Drupal\Core\Routing\RouteMatchInterface');
 
-    $this->controllerResolver->expects($this->exactly(3))
+    $this->controllerResolver->expects($this->exactly(4))
       ->method('getControllerFromDefinition')
       ->willReturnMap([
         ['\Drupal\Tests\Core\Access\TestController::accessDeny', [new TestController(), 'accessDeny']],
         ['\Drupal\Tests\Core\Access\TestController::accessAllow', [new TestController(), 'accessAllow']],
         ['\Drupal\Tests\Core\Access\TestController::accessParameter', [new TestController(), 'accessParameter']],
+        ['\Drupal\Tests\Core\Access\TestController::accessRequest', [new TestController(), 'accessRequest']],
       ]);
 
     $resolver0 = $this->createMock('Drupal\Component\Utility\ArgumentsResolverInterface');
@@ -80,24 +82,33 @@ public function testAccess() {
     $resolver2->expects($this->once())
       ->method('getArguments')
       ->willReturn(['parameter' => 'TRUE']);
+    $request = Request::create('/foo?example=muh');
+    $resolver3 = $this->createMock('Drupal\Component\Utility\ArgumentsResolverInterface');
+    $resolver3->expects($this->once())
+      ->method('getArguments')
+      ->willReturn(['request' => $request]);
 
-    $this->argumentsResolverFactory->expects($this->exactly(3))
+    $this->argumentsResolverFactory->expects($this->exactly(4))
       ->method('getArgumentsResolver')
       ->willReturnOnConsecutiveCalls(
         $resolver0,
         $resolver1,
         $resolver2,
+        $resolver3,
       );
 
     $route = new Route('/test-route', [], ['_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessDeny']);
     $account = $this->createMock('Drupal\Core\Session\AccountInterface');
-    $this->assertEquals(AccessResult::neutral(), $this->accessChecker->access($route, $route_match, $account));
+    $this->assertEquals(AccessResult::neutral(), $this->accessChecker->access($route, $route_match, $account, $request));
 
     $route = new Route('/test-route', [], ['_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessAllow']);
-    $this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $route_match, $account));
+    $this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $route_match, $account, $request));
 
     $route = new Route('/test-route', ['parameter' => 'TRUE'], ['_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessParameter']);
-    $this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $route_match, $account));
+    $this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $route_match, $account, $request));
+
+    $route = new Route('/test-route', ['parameter' => 'TRUE'], ['_custom_access' => '\Drupal\Tests\Core\Access\TestController::accessRequest']);
+    $this->assertEquals(AccessResult::allowed(), $this->accessChecker->access($route, $route_match, $account, $request));
   }
 
   /**
@@ -121,12 +132,13 @@ public function testAccessException() {
     $route = new Route('/test-route', [], ['_custom_access' => '\Drupal\Tests\Core\Access\NonExistentController::nonExistentMethod']);
     $route_match = $this->createMock(RouteMatchInterface::class);
     $account = $this->createMock(AccountInterface::class);
+    $request = Request::create('/foo?example=muh');
 
     $this->expectException(\BadMethodCallException::class);
     $this->expectExceptionMessage('The "\Drupal\Tests\Core\Access\NonExistentController::nonExistentMethod" method is not callable as a _custom_access callback in route "/test-route"');
 
     // Run the access check.
-    $this->accessChecker->access($route, $route_match, $account);
+    $this->accessChecker->access($route, $route_match, $account, $request);
   }
 
 }
@@ -145,4 +157,8 @@ public function accessParameter($parameter) {
     return AccessResult::allowedIf($parameter == 'TRUE');
   }
 
+  public function accessRequest(Request $request) {
+    return AccessResult::allowedIf($request->query->get('example') === 'muh');
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Asset/JsOptimizerUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/JsOptimizerUnitTest.php
index 15f21a22cf0062f0e16a860257e069ab8c6b0e10..3c4623ab228fdb1c3df9573a041dce6b8a046296 100644
--- a/core/tests/Drupal/Tests/Core/Asset/JsOptimizerUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/JsOptimizerUnitTest.php
@@ -24,8 +24,8 @@ class JsOptimizerUnitTest extends UnitTestCase {
    */
   protected function setUp(): void {
     parent::setUp();
-
-    $this->optimizer = new JsOptimizer();
+    $logger = $this->createMock('\Psr\Log\LoggerInterface');
+    $this->optimizer = new JsOptimizer($logger);
   }
 
   /**
@@ -119,6 +119,16 @@ public function providerTestOptimize() {
         ],
         file_get_contents($path . 'to_be_minified.js.optimized.js'),
       ],
+      4 => [
+        [
+          'type' => 'file',
+          'preprocess' => TRUE,
+          'data' => $path . 'syntax_error.js',
+        ],
+        // When there is a syntax error, the 'optimized' contents are the
+        // contents of the original file.
+        file_get_contents($path . 'syntax_error.js'),
+      ],
     ];
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Asset/js_test_files/syntax_error.js b/core/tests/Drupal/Tests/Core/Asset/js_test_files/syntax_error.js
new file mode 100644
index 0000000000000000000000000000000000000000..3794c41c8451c98cfcbbaba6b6839263f3aeebbc
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/js_test_files/syntax_error.js
@@ -0,0 +1,13 @@
+/**
+ * Some comments.
+ */
+
+(function foo() {
+  // Missing closing parenthesis.
+  if (true {
+    'print 1';
+  }
+  else {
+    'print 2';
+  }
+})
diff --git a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
index 6b226965c971dff65697e79bf1fdc0413aea5773..7b5b3f56e3d3d17cf35a5b6968dd1d469f1cf027 100644
--- a/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
+++ b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
@@ -192,7 +192,7 @@ public function testBuildTags($prefix, array $suffixes, array $expected, $glue =
    * @group legacy
    */
   public function testKeyFromQuery() {
-    $this->expectDeprecation('Drupal\Core\Cache\Cache::keyFromQuery is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. No replacement provided. https://www.drupal.org/node/3322044');
+    $this->expectDeprecation('Drupal\Core\Cache\Cache::keyFromQuery is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. No replacement provided. See https://www.drupal.org/node/3322044');
     $query = new Select(new StubConnection(new StubPDO(), []), 'dne');
     Cache::keyFromQuery($query);
   }
diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
index cc58ccd5e903a68c985833337101011b58d6b40e..70e7c059c723804b606e25b31ea65c0029dd5bfe 100644
--- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php
@@ -531,26 +531,22 @@ public function testSort() {
 
     // Test sorting by label.
     $list = [$entity_a, $entity_b];
-    // Suppress errors because of https://bugs.php.net/bug.php?id=50688.
-    @usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort');
+    usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort');
     $this->assertSame($entity_b, $list[0]);
 
     $list = [$entity_b, $entity_a];
-    // Suppress errors because of https://bugs.php.net/bug.php?id=50688.
-    @usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort');
+    usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort');
     $this->assertSame($entity_b, $list[0]);
 
     // Test sorting by weight.
     $entity_a->weight = 0;
     $entity_b->weight = 1;
     $list = [$entity_b, $entity_a];
-    // Suppress errors because of https://bugs.php.net/bug.php?id=50688.
-    @usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort');
+    usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort');
     $this->assertSame($entity_a, $list[0]);
 
     $list = [$entity_a, $entity_b];
-    // Suppress errors because of https://bugs.php.net/bug.php?id=50688.
-    @usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort');
+    usort($list, '\Drupal\Core\Config\Entity\ConfigEntityBase::sort');
     $this->assertSame($entity_a, $list[0]);
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php
index 28670b717533338d4b36e844463d805baff7df56..02c4c6b38fb3fe2a95b4667a8d8442b3505821fd 100644
--- a/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php
+++ b/core/tests/Drupal/Tests/Core/Database/ConnectionTest.php
@@ -5,6 +5,7 @@
 use Composer\Autoload\ClassLoader;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\StatementPrefetch;
+use Drupal\Core\Database\StatementPrefetchIterator;
 use Drupal\Tests\Core\Database\Stub\StubConnection;
 use Drupal\Tests\Core\Database\Stub\StubPDO;
 use Drupal\Tests\UnitTestCase;
@@ -906,4 +907,77 @@ public function testStatementPrefetchDeprecation() {
     $this->assertInstanceOf(StatementPrefetch::class, $statement);
   }
 
+  /**
+   * Provides data for testSupportedFetchModes.
+   *
+   * @return array
+   *   An associative array of simple arrays, each having the following
+   *   elements:
+   *   - a PDO fetch mode.
+   */
+  public static function providerSupportedFetchModes(): array {
+    return [
+      'FETCH_ASSOC' => [\PDO::FETCH_ASSOC],
+      'FETCH_CLASS' => [\PDO::FETCH_CLASS],
+      'FETCH_CLASS | FETCH_PROPS_LATE' => [\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE],
+      'FETCH_COLUMN' => [\PDO::FETCH_COLUMN],
+      'FETCH_NUM' => [\PDO::FETCH_NUM],
+      'FETCH_OBJ' => [\PDO::FETCH_OBJ],
+    ];
+  }
+
+  /**
+   * Tests supported fetch modes.
+   *
+   * @dataProvider providerSupportedFetchModes
+   */
+  public function testSupportedFetchModes(int $mode): void {
+    $mockPdo = $this->createMock(StubPDO::class);
+    $mockConnection = new StubConnection($mockPdo, []);
+    $statement = new StatementPrefetchIterator($mockPdo, $mockConnection, '');
+    $this->assertInstanceOf(StatementPrefetchIterator::class, $statement);
+    $statement->setFetchMode($mode);
+  }
+
+  /**
+   * Provides data for testDeprecatedFetchModes.
+   *
+   * @return array
+   *   An associative array of simple arrays, each having the following
+   *   elements:
+   *   - a PDO fetch mode.
+   */
+  public static function providerDeprecatedFetchModes(): array {
+    return [
+      'FETCH_DEFAULT' => [\PDO::FETCH_DEFAULT],
+      'FETCH_LAZY' => [\PDO::FETCH_LAZY],
+      'FETCH_BOTH' => [\PDO::FETCH_BOTH],
+      'FETCH_BOUND' => [\PDO::FETCH_BOUND],
+      'FETCH_INTO' => [\PDO::FETCH_INTO],
+      'FETCH_FUNC' => [\PDO::FETCH_FUNC],
+      'FETCH_NAMED' => [\PDO::FETCH_NAMED],
+      'FETCH_KEY_PAIR' => [\PDO::FETCH_KEY_PAIR],
+      'FETCH_CLASS | FETCH_CLASSTYPE' => [\PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE],
+    ];
+  }
+
+  /**
+   * Tests deprecated fetch modes.
+   *
+   * @todo in drupal:11.0.0, do not remove this test but convert it to expect
+   *   exceptions instead of deprecations.
+   *
+   * @dataProvider providerDeprecatedFetchModes
+   *
+   * @group legacy
+   */
+  public function testDeprecatedFetchModes(int $mode): void {
+    $this->expectDeprecation('Fetch mode %A is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. Use supported modes only. See https://www.drupal.org/node/3377999');
+    $mockPdo = $this->createMock(StubPDO::class);
+    $mockConnection = new StubConnection($mockPdo, []);
+    $statement = new StatementPrefetchIterator($mockPdo, $mockConnection, '');
+    $this->assertInstanceOf(StatementPrefetchIterator::class, $statement);
+    $statement->setFetchMode($mode);
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Database/UrlConversionTest.php b/core/tests/Drupal/Tests/Core/Database/UrlConversionTest.php
index a4795b2da0b3e8897f2111a4707756169fd60cdb..fd288e0d46b721a81d5d5d6798c2de050e3bc0c9 100644
--- a/core/tests/Drupal/Tests/Core/Database/UrlConversionTest.php
+++ b/core/tests/Drupal/Tests/Core/Database/UrlConversionTest.php
@@ -27,6 +27,8 @@ class UrlConversionTest extends UnitTestCase {
   protected function setUp(): void {
     parent::setUp();
     $this->root = dirname(__FILE__, 7);
+    // This unit test relies on reading files relative to Drupal root.
+    chdir($this->root);
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeRepositoryTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeRepositoryTest.php
index cc71399e8ac7d568cdd4165cafdc04b65277632e..3488d3ea5aa0e073feb181e3c072d26964616f70 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeRepositoryTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeRepositoryTest.php
@@ -155,16 +155,16 @@ public function testGetEntityTypeFromClassNoMatch() {
    * @covers ::getEntityTypeFromClass
    */
   public function testGetEntityTypeFromClassAmbiguous() {
-    $boskoop = $this->prophesize(EntityTypeInterface::class);
-    $boskoop->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
-    $boskoop->id()->willReturn('boskop');
+    $jazz = $this->prophesize(EntityTypeInterface::class);
+    $jazz->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
+    $jazz->id()->willReturn('jazz');
 
     $gala = $this->prophesize(EntityTypeInterface::class);
     $gala->getOriginalClass()->willReturn('\Drupal\apple\Entity\Apple');
     $gala->id()->willReturn('gala');
 
     $this->setUpEntityTypeDefinitions([
-      'boskoop' => $boskoop,
+      'jazz' => $jazz,
       'gala' => $gala,
     ]);
 
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php
index 9af542afa2060859d0bb2ec8bb36e1b6b2a2ded2..cd9dc82ce7757a9054a7dc801d1ce94d8ed591f0 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityUrlTest.php
@@ -107,6 +107,30 @@ public function testToUrlNoId() {
     $entity->toUrl();
   }
 
+  /**
+   * Tests the toUrl() method without specifying the $rel parameter..
+   *
+   * @covers ::toUrl
+   */
+  public function testToUrlDefault() {
+    $values = ['id' => $this->entityId];
+    $entity = $this->getEntity(UrlTestEntity::class, $values);
+
+    $this->expectException(UndefinedLinkTemplateException::class);
+    $this->expectExceptionMessage("Cannot generate default URL because no link template 'canonical' or 'edit-form' was found for the '" . $this->entityTypeId . "' entity type");
+    $entity->toUrl();
+
+    $this->registerLinkTemplate('edit-form');
+    /** @var \Drupal\Core\Url $url */
+    $url = $entity->toUrl();
+    $this->assertUrl('entity.test_entity.edit_form', ['test_entity' => $this->entityId], $entity, FALSE, $url);
+
+    $this->registerLinkTemplate('canonical');
+    /** @var \Drupal\Core\Url $url */
+    $url = $entity->toUrl();
+    $this->assertUrl('entity.test_entity.canonical', ['test_entity' => $this->entityId], $entity, FALSE, $url);
+  }
+
   /**
    * Tests the toUrl() method with simple link templates.
    *
diff --git a/core/tests/Drupal/Tests/Core/Extension/GenericTestExistsTest.php b/core/tests/Drupal/Tests/Core/Extension/GenericTestExistsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a879b691f9c1cdc2319d43702119dc2e4bb7cf70
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Extension/GenericTestExistsTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Tests\Core\Extension;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait;
+
+/**
+ * Tests that the Generic module test exists for all modules.
+ *
+ * @group Extension
+ */
+class GenericTestExistsTest extends UnitTestCase {
+
+  use FileSystemModuleDiscoveryDataProviderTrait;
+
+  /**
+   * Lists module that do not require a Generic test.
+   */
+  protected $modulesNoTest = ['help_topics'];
+
+  /**
+   * Tests that the Generic module test exists for all modules.
+   *
+   * @dataProvider coreModuleListDataProvider
+   */
+  public function testGenericTestExists(string $module_name): void {
+    if (in_array($module_name, $this->modulesNoTest, TRUE)) {
+      $this->markTestSkipped();
+    }
+    $this->assertFileExists("{$this->root}/core/modules/{$module_name}/tests/src/Functional/GenericTest.php");
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php b/core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php
index d1495335f354af47edcb540ba561475d68ca5fbd..fe3b81afbc73f26d5854b3d50a4ac25012c99d55 100644
--- a/core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/EventSubscriber/FormAjaxSubscriberTest.php
@@ -167,19 +167,8 @@ public function testOnExceptionBrokenPostRequest() {
     $this->messenger->expects($this->once())
       ->method('addError');
 
-    $this->subscriber = $this->getMockBuilder('\Drupal\Core\Form\EventSubscriber\FormAjaxSubscriber')
-      ->setConstructorArgs([
-        $this->formAjaxResponseBuilder,
-        $this->getStringTranslationStub(),
-        $this->messenger,
-      ])
-      ->onlyMethods(['formatSize'])
-      ->getMock();
-
-    $this->subscriber->expects($this->once())
-      ->method('formatSize')
-      ->with(32 * 1e6)
-      ->willReturn('32M');
+    $this->subscriber = new FormAjaxSubscriber($this->formAjaxResponseBuilder, $this->getStringTranslationStub(), $this->messenger);
+
     $rendered_output = 'the rendered output';
     // CommandWithAttachedAssetsTrait::getRenderedContent() will call the
     // renderer service via the container.
diff --git a/core/tests/Drupal/Tests/Core/StringTranslation/ByteSizeMarkupTest.php b/core/tests/Drupal/Tests/Core/StringTranslation/ByteSizeMarkupTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a0eb2e40c0309dc373edb457b9e3049c16707dd3
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/StringTranslation/ByteSizeMarkupTest.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Drupal\Tests\Core\StringTranslation;
+
+use Drupal\Component\Utility\Bytes;
+use Drupal\Core\StringTranslation\ByteSizeMarkup;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\StringTranslation\ByteSizeMarkup
+ * @group StringTranslation
+ */
+class ByteSizeMarkupTest extends UnitTestCase {
+
+  /**
+   * @covers ::create
+   * @dataProvider providerTestCommonFormatSize
+   */
+  public function testCommonFormatSize($expected, $input) {
+    $size = ByteSizeMarkup::create($input, NULL, $this->getStringTranslationStub());
+    $this->assertInstanceOf(TranslatableMarkup::class, $size);
+    $this->assertEquals($expected, $size);
+  }
+
+  /**
+   * Provides a list of byte size to test.
+   */
+  public function providerTestCommonFormatSize() {
+    $kb = Bytes::KILOBYTE;
+    return [
+      ['0 bytes', 0],
+      // @todo https://www.drupal.org/node/3161118 Prevent display of fractional
+      //   bytes for size less then 1KB.
+      ['0.1 bytes', 0.1],
+      ['0.6 bytes', 0.6],
+      ['1 byte', 1],
+      ['-1 bytes', -1],
+      ['2 bytes', 2],
+      ['-2 bytes', -2],
+      ['1023 bytes', $kb - 1],
+      ['1 KB', $kb],
+      ['1 MB', pow($kb, 2)],
+      ['1 GB', pow($kb, 3)],
+      ['1 TB', pow($kb, 4)],
+      ['1 PB', pow($kb, 5)],
+      ['1 EB', pow($kb, 6)],
+      ['1 ZB', pow($kb, 7)],
+      ['1 YB', pow($kb, 8)],
+      ['1024 YB', pow($kb, 9)],
+      // Rounded to 1 MB - not 1000 or 1024 kilobytes
+      ['1 MB', ($kb * $kb) - 1],
+      ['-1 MB', -(($kb * $kb) - 1)],
+      // Decimal Megabytes
+      ['3.46 MB', 3623651],
+      ['3.77 GB', 4053371676],
+      // Decimal Petabytes
+      ['59.72 PB', 67234178751368124],
+      // Decimal Yottabytes
+      ['194.67 YB', 235346823821125814962843827],
+    ];
+  }
+
+  /**
+   * @covers ::create
+   */
+  public function testTranslatableMarkupObject(): void {
+    $result = ByteSizeMarkup::create(1, NULL, $this->getStringTranslationStub());
+    $this->assertInstanceOf(PluralTranslatableMarkup::class, $result);
+    $this->assertEquals("1 byte\03@count bytes", $result->getUntranslatedString());
+
+    $result = ByteSizeMarkup::create(1048576, 'fr', $this->getStringTranslationStub());
+    $this->assertInstanceOf(TranslatableMarkup::class, $result);
+    $this->assertEquals("@size MB", $result->getUntranslatedString());
+    $this->assertEquals('fr', $result->getOption('langcode'));
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Test/TestDiscoveryTest.php b/core/tests/Drupal/Tests/Core/Test/TestDiscoveryTest.php
index ca05d32c7730be014bb944ad1cdc5d734ea2d270..9d9119f7b579cbcad5c61cd8231555af18a90798 100644
--- a/core/tests/Drupal/Tests/Core/Test/TestDiscoveryTest.php
+++ b/core/tests/Drupal/Tests/Core/Test/TestDiscoveryTest.php
@@ -62,7 +62,7 @@ public function infoParserProvider() {
       [
         'name' => 'Drupal\FunctionalTests\BrowserTestBaseTest',
         'group' => 'browsertestbase',
-        'groups' => ['browsertestbase'],
+        'groups' => ['browsertestbase', '#slow'],
         'description' => 'Tests BrowserTestBase functionality.',
         'type' => 'PHPUnit-Functional',
       ],
diff --git a/core/tests/Drupal/Tests/Core/Utility/TokenTest.php b/core/tests/Drupal/Tests/Core/Utility/TokenTest.php
index d9c102f372a591c691ba0554a6770fd689baaf03..c731a00e12263fa234176181d80415bc1d0936b1 100644
--- a/core/tests/Drupal/Tests/Core/Utility/TokenTest.php
+++ b/core/tests/Drupal/Tests/Core/Utility/TokenTest.php
@@ -335,7 +335,7 @@ public function testScan() {
    * @group legacy
    */
   public function testScanDeprecation() {
-    $this->expectDeprecation('Calling Drupal\Core\Utility\Token::scan() with a $text parameter of type other than string is deprecated in drupal:10.1.0, a typehint will be added in drupal:11.0.0. See https://www.drupal.org/node/3334317');
+    $this->expectDeprecation('Calling Drupal\Core\Utility\Token::scan() with a $text parameter of type other than string is deprecated in drupal:10.1.0 and will cause an error in drupal:11.0.0. See https://www.drupal.org/node/3334317');
     $this->assertSame([], $this->token->scan(NULL));
   }
 
diff --git a/core/tests/Drupal/Tests/ExpectDeprecationTest.php b/core/tests/Drupal/Tests/ExpectDeprecationTest.php
index f5e49fc9504bb5dcfa946fddd9618c43939d7054..be62d5d4cb71dca37477d088eebf1ed374e2971d 100644
--- a/core/tests/Drupal/Tests/ExpectDeprecationTest.php
+++ b/core/tests/Drupal/Tests/ExpectDeprecationTest.php
@@ -19,6 +19,7 @@ class ExpectDeprecationTest extends TestCase {
    */
   public function testExpectDeprecation() {
     $this->expectDeprecation('Test deprecation');
+    // phpcs:ignore Drupal.Semantics.FunctionTriggerError
     @trigger_error('Test deprecation', E_USER_DEPRECATED);
   }
 
@@ -30,6 +31,7 @@ public function testExpectDeprecation() {
    */
   public function testExpectDeprecationInIsolation() {
     $this->expectDeprecation('Test isolated deprecation');
+    // phpcs:ignore Drupal.Semantics.FunctionTriggerError
     @trigger_error('Test isolated deprecation', E_USER_DEPRECATED);
   }
 
diff --git a/core/tests/Drupal/Tests/Listeners/DrupalListener.php b/core/tests/Drupal/Tests/Listeners/DrupalListener.php
index ec1eb30ec1948bd7a88c03a9d11dfac2b28fb404..b1c895e02a6af45b7e1fc00ffbaffc0f164aea74 100644
--- a/core/tests/Drupal/Tests/Listeners/DrupalListener.php
+++ b/core/tests/Drupal/Tests/Listeners/DrupalListener.php
@@ -54,7 +54,7 @@ public function startTest(Test $test): void {
     // Check for incorrect visibility of the $modules property.
     $class = new \ReflectionClass($test);
     if ($class->hasProperty('modules') && !$class->getProperty('modules')->isProtected()) {
-      @trigger_error('The ' . get_class($test) . '::$modules property must be declared protected. See https://www.drupal.org/node/2909426', E_USER_DEPRECATED);
+      @trigger_error('Declaring ' . get_class($test) . '::$modules with public visibility is deprecated in drupal:9.1.0 and must be declared protected in drupal:10.0.0. See https://www.drupal.org/node/2909426', E_USER_DEPRECATED);
     }
   }
 
diff --git a/core/tests/Drupal/Tests/PerformanceData.php b/core/tests/Drupal/Tests/PerformanceData.php
new file mode 100644
index 0000000000000000000000000000000000000000..528fefd81c9551a618c36708e7c25ba2cf89e701
--- /dev/null
+++ b/core/tests/Drupal/Tests/PerformanceData.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\Tests;
+
+/**
+ * Value object to store performance information collected from requests.
+ *
+ * @see Drupal\Tests\PerformanceTestTrait::collectPerformanceData().
+ */
+class PerformanceData {
+
+  /**
+   * The number of stylesheets requested.
+   */
+  protected int $stylesheetCount = 0;
+
+  /**
+   * The number of scripts requested.
+   */
+  protected int $scriptCount = 0;
+
+  /**
+   * The original return value.
+   */
+  protected $returnValue;
+
+  /**
+   * Sets the stylesheet request count.
+   *
+   * @param int $count
+   *   The number of stylesheet requests recorded.
+   */
+  public function setStylesheetCount(int $count): void {
+    $this->stylesheetCount = $count;
+  }
+
+  /**
+   * Gets the stylesheet request count.
+   *
+   * @return int
+   *   The number of stylesheet requests recorded.
+   */
+  public function getStylesheetCount(): int {
+    return $this->stylesheetCount;
+  }
+
+  /**
+   * Sets the script request count.
+   *
+   * @param int $count
+   *   The number of script requests recorded.
+   */
+  public function setScriptCount(int $count) {
+    $this->scriptCount = $count;
+  }
+
+  /**
+   * Gets the script request count.
+   *
+   * @return int
+   *   The number of script requests recorded.
+   */
+  public function getScriptCount(): int {
+    return $this->scriptCount;
+  }
+
+  /**
+   * Sets the original return value.
+   *
+   * @param mixed $return
+   *   The original return value.
+   */
+  public function setReturnValue($return): void {
+    $this->returnValue = $return;
+  }
+
+  /**
+   * Gets the original return value.
+   *
+   * PerformanceTestTrait::collectPerformanceData() takes a callable as its
+   * argument. This method allows the original return value of the callable to
+   * be retrieved.
+   *
+   * @return mixed
+   *   The original return value.
+   */
+  public function getReturnValue() {
+    return $this->returnValue;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/PerformanceTestTrait.php b/core/tests/Drupal/Tests/PerformanceTestTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..cc4122ef17a9c04340a402ca01543971660c6fe8
--- /dev/null
+++ b/core/tests/Drupal/Tests/PerformanceTestTrait.php
@@ -0,0 +1,327 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Tests;
+
+use OpenTelemetry\API\Trace\SpanKind;
+use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
+use OpenTelemetry\Contrib\Otlp\SpanExporter;
+use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
+use OpenTelemetry\SDK\Trace\TracerProvider;
+use OpenTelemetry\SDK\Resource\ResourceInfo;
+use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
+use OpenTelemetry\SDK\Common\Attribute\Attributes;
+use OpenTelemetry\SemConv\ResourceAttributes;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides various methods to aid in collecting performance data during tests.
+ *
+ * @ingroup testing
+ */
+trait PerformanceTestTrait {
+
+  /**
+   * Helper for ::setUp().
+   *
+   * Resets configuration to be closer to production settings.
+   *
+   * @see \Drupal\Tests\BrowserTestBase::setUp()
+   */
+  private function doSetUpTasks(): void {
+    \Drupal::configFactory()->getEditable('system.performance')
+      ->set('css.preprocess', TRUE)
+      ->set('js.preprocess', TRUE)
+      ->save();
+  }
+
+  /**
+   * Helper for ::installModulesFromClassProperty().
+   *
+   * To use this, override BrowserTestBase::installModulesFromClassProperty()
+   * and call this helper.
+   *
+   * @see \Drupal\Tests\BrowserTestBase::installModulesFromClassProperty()
+   */
+  private function doInstallModulesFromClassProperty(ContainerInterface $container) {
+    // Bypass everything that WebDriverTestBase does here to get closer to
+    // a production configuration.
+    BrowserTestBase::installModulesFromClassProperty($container);
+  }
+
+  /**
+   * Helper for ::getMinkDriverArgs().
+   *
+   * To use this, override BrowserTestBase::getMinkDriverArgs() and call this
+   * helper.
+   *
+   * @return string
+   *   The JSON encoded driver args with performance logging preferences added.
+   *
+   * @see \Drupal\Tests\BrowserTestBase::getMinkDriverArgs()
+   */
+  private function doGetMinkDriverArgs(): string {
+    // Add performance logging preferences to the existing driver arguments to
+    // avoid clobbering anything set via environment variables.
+    // @see https://chromedriver.chromium.org/logging/performance-log
+    $parent_driver_args = parent::getMinkDriverArgs();
+    $driver_args = json_decode($parent_driver_args, TRUE);
+
+    $driver_args[1]['goog:loggingPrefs'] = [
+      'browser' => 'ALL',
+      'performance' => 'ALL',
+      'performanceTimeline' => 'ALL',
+    ];
+    $driver_args[1]['chromeOptions']['perfLoggingPrefs'] = [
+      'traceCategories' => 'timeline,devtools.timeline,browser',
+    ];
+
+    return json_encode($driver_args);
+  }
+
+  /**
+   * Executes a callable and collects performance data.
+   *
+   * @param callable $callable
+   *   A callable, for example ::drupalGet().
+   * @param string|null $service_name
+   *   An optional human readable identifier to enable sending traces to an Open
+   *   Telemetry endpoint (if configured).
+   *
+   * @return \Drupal\Tests\PerformanceData
+   *   A PerformanceData value object.
+   */
+  public function collectPerformanceData(callable $callable, ?string $service_name = NULL): PerformanceData {
+    $session = $this->getSession();
+    $session->getDriver()->getWebDriverSession()->log('performance');
+    $return = $callable();
+    $performance_data = $this->processChromeDriverPerformanceLogs($service_name);
+    if (isset($return)) {
+      $performance_data->setReturnValue($performance_data);
+    }
+
+    return $performance_data;
+  }
+
+  /**
+   * Gets the chromedriver performance log and extracts metrics from it.
+   *
+   * The performance log is cumulative, and is emptied each time it is
+   * collected. If the log grows to the point it will overflow, it may also be
+   * emptied resulting in lost messages. There is no specific
+   * LargestContentfulPaint event, instead there are
+   * largestContentfulPaint::Candidate events which may be superseded by later
+   * events. From manual testing none of the core pages result in more than
+   * two largestContentfulPaint::Candidate events, so we keep looking until
+   * either two have been sent, or until 30 seconds has passed.
+   *
+   * @todo https://www.drupal.org/project/drupal/issues/3379757
+   *
+   * @param string|null $service_name
+   *   An optional human readable identifier so that traces can be grouped together.
+   *
+   * @return \Drupal\Tests\PerformanceData
+   *   An instance of the performance data value object.
+   */
+  protected function processChromeDriverPerformanceLogs(?string $service_name): PerformanceData {
+    $attempts = 0;
+    $lcp_count = 0;
+    $messages = [];
+    $session = $this->getSession();
+    while ($attempts <= 30) {
+      $attempts++;
+      $performance_log = $session->getDriver()->getWebDriverSession()->log('performance');
+
+      foreach ($performance_log as $entry) {
+        $decoded = json_decode($entry['message'], TRUE);
+        $message = $decoded['message'];
+        if ($message['method'] === 'Tracing.dataCollected' && $message['params']['name'] === 'largestContentfulPaint::Candidate') {
+          $lcp_count++;
+        }
+        $messages[] = $message;
+      }
+      // Only check once if $service_name is not set, since
+      // largestContentfulPaint is not currently asserted on.
+      if ($lcp_count === 2 || !isset($service_name)) {
+        break;
+      }
+      sleep(1);
+    }
+    $performance_data = new PerformanceData();
+    $this->collectNetworkData($messages, $performance_data);
+
+    if (isset($service_name)) {
+      $this->openTelemetryTracing($messages, $service_name);
+    }
+
+    return $performance_data;
+  }
+
+  /**
+   * Prepares data for assertions.
+   *
+   * @param array $messages
+   *   The chromedriver performance log messages.
+   * @param \Drupal\Tests\PerformanceData $performance_data
+   *   An instance of the performance data value object.
+   */
+  private function collectNetworkData(array $messages, PerformanceData $performance_data): void {
+    $stylesheet_count = 0;
+    $script_count = 0;
+    foreach ($messages as $message) {
+      if ($message['method'] === 'Network.responseReceived') {
+        if ($message['params']['type'] === 'Stylesheet') {
+          $stylesheet_count++;
+        }
+        if ($message['params']['type'] === 'Script') {
+          $script_count++;
+        }
+      }
+    }
+    $performance_data->setStylesheetCount($stylesheet_count);
+    $performance_data->setScriptCount($script_count);
+  }
+
+  /**
+   * Sends metrics to OpenTelemetry.
+   *
+   * @param array $messages
+   *   The ChromeDriver performance log messages.
+   * @param string $service_name
+   *   A human readable identifier so that traces can be grouped together.
+   *
+   * @see https://opentelemetry.io/docs/instrumentation/php/manual/
+   */
+  private function openTelemetryTracing(array $messages, string $service_name): void {
+    // Open telemetry timestamps are always in nanoseconds.
+    // @todo: consider moving these to trait constants once we require PHP 8.2.
+    $nanoseconds_per_second = 1000_000_000;
+    $nanoseconds_per_millisecond = 1000_000;
+    $nanoseconds_per_microsecond = 1000;
+
+    $collector = $_ENV['OTEL_COLLECTOR'] ?? NULL;
+    if ($collector === NULL) {
+      return;
+    }
+    $timestamp = NULL;
+    $url = NULL;
+    $dom_loaded_timestamp_page = NULL;
+    $dom_loaded_timestamp_timeline = NULL;
+    $timestamp_since_os_boot = NULL;
+    foreach ($messages as $message) {
+      // Since chrome timestamps are since OS start, we take the first network
+      // request as '0' and calculate offsets against that.
+      if ($timestamp === NULL && $message['method'] === 'Network.requestWillBeSent') {
+        $url = $message['params']['request']['url'];
+        $timestamp = (int) ($message['params']['wallTime'] * $nanoseconds_per_second);
+        // Network timestamps are formatted as a second float with three point
+        // precision. Record this so it can be compared against other
+        // timestamps.
+        $timestamp_since_os_boot = (int) ($message['params']['timestamp'] * $nanoseconds_per_second);
+      }
+      // The DOM content loaded event is in both the 'page' and 'timeline'
+      // sections of the performance log in different formats. This lets us
+      // compare 'ts' and 'timestamp' which are not only in two different
+      // formats, but appear to start from slightly different points in time.
+      // By subtracting one from the other, we can generate an offset to apply
+      // to all other 'ts' timestamps. Note that if the two events actually
+      // happen at different times, then the offset will be wrong by that
+      // difference.
+      // See https://bugs.chromium.org/p/chromium/issues/detail?id=1463436
+      if ($dom_loaded_timestamp_page === NULL && $message['method'] === 'Page.domContentEventFired') {
+        $dom_loaded_timestamp_page = $message['params']['timestamp'] * $nanoseconds_per_second;
+      }
+      if ($dom_loaded_timestamp_timeline === NULL && $message['method'] === 'Tracing.dataCollected' && isset($message['params']['args']['data']['type']) && $message['params']['args']['data']['type'] === 'DOMContentLoaded') {
+        $dom_loaded_timestamp_timeline = $message['params']['ts'] * $nanoseconds_per_microsecond;
+      }
+    }
+
+    $offset = $dom_loaded_timestamp_page - $dom_loaded_timestamp_timeline;
+    $entry = $this->getSession()->evaluateScript("window.performance.getEntriesByType('navigation')")[0];
+    $first_request_timestamp = $entry['requestStart'] * $nanoseconds_per_millisecond;
+    $first_response_timestamp = $entry['responseStart'] * $nanoseconds_per_millisecond;
+
+    // @todo: get commit hash from an environment variable and add this as an
+    // additional attribute.
+    // @see https://www.drupal.org/project/drupal/issues/3379761
+    $resource = ResourceInfoFactory::merge(ResourceInfo::create(Attributes::create([
+      ResourceAttributes::SERVICE_NAMESPACE => 'Drupal',
+      ResourceAttributes::SERVICE_NAME => $service_name,
+      ResourceAttributes::SERVICE_INSTANCE_ID => 1,
+      ResourceAttributes::SERVICE_VERSION => \Drupal::VERSION,
+      ResourceAttributes::DEPLOYMENT_ENVIRONMENT => 'local',
+    ])), ResourceInfoFactory::defaultResource());
+
+    $transport = (new OtlpHttpTransportFactory())->create($collector, 'application/x-protobuf');
+    $exporter = new SpanExporter($transport);
+    $tracerProvider = new TracerProvider(new SimpleSpanProcessor($exporter), NULL, $resource);
+    $tracer = $tracerProvider->getTracer('Drupal');
+
+    $span = $tracer->spanBuilder('main')
+      ->setStartTimestamp($timestamp)
+      ->setAttribute('http.method', 'GET')
+      ->setAttribute('http.url', $url)
+      ->setSpanKind(SpanKind::KIND_SERVER)
+      ->startSpan();
+    $last_timestamp = $first_byte_timestamp = (int) ($timestamp + ($first_response_timestamp - $first_request_timestamp));
+
+    try {
+      $scope = $span->activate();
+      $first_byte_span = $tracer->spanBuilder('firstByte')
+        ->setStartTimestamp($timestamp)
+        ->setAttribute('http.url', $url)
+        ->startSpan();
+      $first_byte_span->end($first_byte_timestamp);
+      // Largest contentful paint is not available from
+      // window.performance::getEntriesByType() so use the performance log
+      // messages to get it instead.
+      $lcp_timestamp = NULL;
+      $fcp_timestamp = NULL;
+      $lcp_size = 0;
+      foreach ($messages as $message) {
+        if ($message['method'] === 'Tracing.dataCollected' && $message['params']['name'] === 'firstContentfulPaint') {
+          if (!isset($fcp_timestamp)) {
+            // Tracing timestamps are microseconds since OS boot. However they
+            // appear to start from a slightly different point from page
+            // timestamps. Apply an offset calculated from DOM content loaded.
+            // See https://bugs.chromium.org/p/chromium/issues/detail?id=1463436
+            $fcp_timestamp = ($message['params']['ts'] * $nanoseconds_per_microsecond) + $offset;
+            $fcp_span = $tracer->spanBuilder('firstContentfulPaint')
+              ->setStartTimestamp($timestamp)
+              ->setAttribute('http.url', $url)
+              ->startSpan();
+            $last_timestamp = $first_contentful_paint_timestamp = (int) ($timestamp + ($fcp_timestamp - $timestamp_since_os_boot));
+            $fcp_span->end($first_contentful_paint_timestamp);
+          }
+        }
+
+        // There can be multiple largestContentfulPaint candidates, remember
+        // the largest one.
+        if ($message['method'] === 'Tracing.dataCollected' && $message['params']['name'] === 'largestContentfulPaint::Candidate' && $message['params']['args']['data']['size'] > $lcp_size) {
+          $lcp_timestamp = ($message['params']['ts'] * $nanoseconds_per_microsecond) + $offset;
+          $lcp_size = $message['params']['args']['data']['size'];
+        }
+      }
+      if (isset($lcp_timestamp)) {
+        $lcp_span = $tracer->spanBuilder('largestContentfulPaint')
+          ->setStartTimestamp($timestamp)
+          ->setAttribute('http.url', $url)
+          ->startSpan();
+        $last_timestamp = $largest_contentful_paint_timestamp = (int) ($timestamp + ($lcp_timestamp - $timestamp_since_os_boot));
+        $lcp_span->setAttribute('lcp.size', $lcp_size);
+        $lcp_span->end($largest_contentful_paint_timestamp);
+      }
+    }
+    finally {
+      // The scope must be detached before the span is ended, because it's
+      // created from the span.
+      if (isset($scope)) {
+        $scope->detach();
+      }
+      $span->end($last_timestamp);
+      $tracerProvider->shutdown();
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Scripts/TestSiteApplicationTest.php b/core/tests/Drupal/Tests/Scripts/TestSiteApplicationTest.php
index fcb63ccd4bfd22ac13963f3db1faf1001def94ab..490e49242ad7a1da55f980d445372d276f507a7b 100644
--- a/core/tests/Drupal/Tests/Scripts/TestSiteApplicationTest.php
+++ b/core/tests/Drupal/Tests/Scripts/TestSiteApplicationTest.php
@@ -49,36 +49,22 @@ protected function setUp(): void {
    * @coversNothing
    */
   public function testInstallWithNonExistingFile() {
-
-    // Create a connection to the DB configured in SIMPLETEST_DB.
-    $connection = Database::getConnection('default', $this->addTestDatabase(''));
-    $table_count = count($connection->schema()->findTables('%'));
-
     $command_line = $this->php . ' core/scripts/test-site.php install --setup-file "this-class-does-not-exist" --db-url "' . getenv('SIMPLETEST_DB') . '"';
     $process = Process::fromShellCommandline($command_line, $this->root);
     $process->run();
 
     $this->assertStringContainsString('The file this-class-does-not-exist does not exist.', $process->getErrorOutput());
-    $this->assertSame(1, $process->getExitCode());
-    $this->assertCount($table_count, $connection->schema()->findTables('%'), 'No additional tables created in the database');
   }
 
   /**
    * @coversNothing
    */
   public function testInstallWithFileWithNoClass() {
-
-    // Create a connection to the DB configured in SIMPLETEST_DB.
-    $connection = Database::getConnection('default', $this->addTestDatabase(''));
-    $table_count = count($connection->schema()->findTables('%'));
-
     $command_line = $this->php . ' core/scripts/test-site.php install --setup-file core/tests/fixtures/empty_file.php.module --db-url "' . getenv('SIMPLETEST_DB') . '"';
     $process = Process::fromShellCommandline($command_line, $this->root);
     $process->run();
 
     $this->assertStringContainsString('The file core/tests/fixtures/empty_file.php.module does not contain a class', $process->getErrorOutput());
-    $this->assertSame(1, $process->getExitCode());
-    $this->assertCount($table_count, $connection->schema()->findTables('%'), 'No additional tables created in the database');
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/UiHelperTrait.php b/core/tests/Drupal/Tests/UiHelperTrait.php
index 61fb51c3d27313cbf14126f86fac19ff71d39bc8..7c2e59992fec71ae060c8735ae9e627194f12726 100644
--- a/core/tests/Drupal/Tests/UiHelperTrait.php
+++ b/core/tests/Drupal/Tests/UiHelperTrait.php
@@ -272,6 +272,8 @@ protected function drupalGet($path, array $options = [], array $headers = []) {
    *   An absolute URL string.
    */
   protected function buildUrl($path, array $options = []) {
+    global $base_path;
+
     if ($path instanceof Url) {
       $url_options = $path->getOptions();
       $options = $url_options + $options;
@@ -281,6 +283,12 @@ protected function buildUrl($path, array $options = []) {
     // The URL generator service is not necessarily available yet; e.g., in
     // interactive installer tests.
     elseif (\Drupal::hasService('url_generator')) {
+      // Strip $base_path, if existent.
+      $length = strlen($base_path);
+      if (substr($path, 0, $length) === $base_path) {
+        $path = substr($path, $length);
+      }
+
       $force_internal = isset($options['external']) && $options['external'] == FALSE;
       if (!$force_internal && UrlHelper::isExternal($path)) {
         return Url::fromUri($path, $options)->toString();
diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php
index 0e1773deac4758bb1bb3b597058403bf5d19dfb1..9306d7d36222cf6f9936eda35c13c5aeae844bf5 100644
--- a/core/tests/Drupal/Tests/UnitTestCase.php
+++ b/core/tests/Drupal/Tests/UnitTestCase.php
@@ -86,9 +86,8 @@ public function __get(string $name) {
    *   configuration object names and whose values are key => value arrays for
    *   the configuration object in question. Defaults to an empty array.
    *
-   * @return \PHPUnit\Framework\MockObject\MockBuilder
-   *   A MockBuilder object for the ConfigFactory with the desired return
-   *   values.
+   * @return \PHPUnit\Framework\MockObject\MockObject|\Drupal\Core\Config\ConfigFactoryInterface
+   *   A mock configuration factory object.
    */
   public function getConfigFactoryStub(array $configs = []) {
     $config_get_map = [];
diff --git a/core/tests/Drupal/Tests/WebAssert.php b/core/tests/Drupal/Tests/WebAssert.php
index ee4408c90ae3aa33042161979a66e64115e8c3dc..d8f9006e3fd5c5ecfb3d89c3bac1405eb934327c 100644
--- a/core/tests/Drupal/Tests/WebAssert.php
+++ b/core/tests/Drupal/Tests/WebAssert.php
@@ -223,7 +223,7 @@ public function optionExists($select, $option, TraversableElement $container = N
     $option_field = $select_field->find('named_exact', ['option', $option]);
 
     if ($option_field === NULL) {
-      throw new ElementNotFoundException($this->session->getDriver(), 'select', 'id|name|label|value', $option);
+      throw new ElementNotFoundException($this->session->getDriver(), 'option', 'id|name|label|value', $option);
     }
 
     return $option_field;
@@ -387,6 +387,29 @@ public function linkByHrefExists($href, $index = 0, $message = '') {
     $this->assert(!empty($links[$index]), $message);
   }
 
+  /**
+   * Passes if a link with a given href is found.
+   *
+   * @param string $href
+   *   The full value of the 'href' attribute of the anchor tag.
+   * @param int $index
+   *   Link position counting from zero.
+   * @param string $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
+   *   variables in the message text, not t(). If left blank, a default message
+   *   will be displayed.
+   *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   *   Thrown when element doesn't exist, or the link label is a different one.
+   */
+  public function linkByHrefExistsExact(string $href, int $index = 0, string $message = ''): void {
+    $xpath = $this->buildXPathQuery('//a[@href=:href]', [':href' => $href]);
+    $message = ($message ?: strtr('No link with href %href found.', ['%href' => $href]));
+    $links = $this->session->getPage()->findAll('xpath', $xpath);
+    $this->assert(!empty($links[$index]), $message);
+  }
+
   /**
    * Passes if a link containing a given href (part) is not found.
    *
@@ -408,6 +431,27 @@ public function linkByHrefNotExists($href, $message = '') {
     $this->assert(empty($links), $message);
   }
 
+  /**
+   * Passes if a link with a given href is not found.
+   *
+   * @param string $href
+   *   The full value of the 'href' attribute of the anchor tag.
+   * @param string $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use \Drupal\Component\Render\FormattableMarkup to embed
+   *   variables in the message text, not t(). If left blank, a default message
+   *   will be displayed.
+   *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   *   Thrown when element doesn't exist, or the link label is a different one.
+   */
+  public function linkByHrefNotExistsExact(string $href, string $message = ''): void {
+    $xpath = $this->buildXPathQuery('//a[@href=:href]', [':href' => $href]);
+    $message = ($message ?: strtr('Link with href %href found.', ['%href' => $href]));
+    $links = $this->session->getPage()->findAll('xpath', $xpath);
+    $this->assert(empty($links), $message);
+  }
+
   /**
    * Builds an XPath query.
    *
diff --git a/core/themes/claro/claro.info.yml b/core/themes/claro/claro.info.yml
index 4d178b8167d18afdbd6f284e98c6586dccf989ab..92f2b14cc4c0d3735f94e2dbec27d0041a0f43cf 100644
--- a/core/themes/claro/claro.info.yml
+++ b/core/themes/claro/claro.info.yml
@@ -47,12 +47,7 @@ libraries-override:
       component:
         misc/vertical-tabs.css: false
 
-  core/drupal.autocomplete:
-    css:
-      theme:
-        assets/vendor/jquery.ui/themes/base/theme.css: false
-
-  core/drupal.tabbingmanager:
+  core/internal.jquery_ui:
     css:
       theme:
         assets/vendor/jquery.ui/themes/base/theme.css: false
@@ -61,8 +56,6 @@ libraries-override:
     css:
       component:
         assets/vendor/jquery.ui/themes/base/dialog.css: false
-      theme:
-        assets/vendor/jquery.ui/themes/base/theme.css: false
 
   user/drupal.user:
     css:
@@ -109,7 +102,6 @@ libraries-extend:
     - claro/details-focus
   core/drupal.dialog:
     - claro/claro.drupal.dialog
-    - claro/claro.jquery.ui
   core/drupal.dropbutton:
     - claro/dropbutton
   core/drupal.checkbox:
@@ -119,7 +111,7 @@ libraries-extend:
   core/drupal.progress:
     - claro/progress
   core/drupal.tabbingmanager:
-    - claro/claro.jquery.ui
+    - claro/tabbingmanager
   core/drupal.tabledrag:
     - claro/claro.tabledrag
   core/drupal.tableselect:
@@ -136,7 +128,6 @@ libraries-extend:
     - claro/system.admin
   core/drupal.autocomplete:
     - claro/autocomplete
-    - claro/claro.jquery.ui
   tour/tour-styling:
     - claro/tour-styling
   shortcut/drupal.shortcut:
diff --git a/core/themes/claro/claro.libraries.yml b/core/themes/claro/claro.libraries.yml
index 2a5aac14ab71459ba342f56f7231b12da385dd20..7a55c1749f06018fd761465c94eacba948375388 100644
--- a/core/themes/claro/claro.libraries.yml
+++ b/core/themes/claro/claro.libraries.yml
@@ -127,6 +127,8 @@ claro.drupal.dialog:
   css:
     theme:
       css/components/dialog.css: {}
+  dependencies:
+    - claro/claro.jquery.ui
 
 claro.tabledrag:
   version: VERSION
@@ -192,6 +194,7 @@ autocomplete:
     js/autocomplete.js: {}
   dependencies:
     - core/once
+    - claro/claro.jquery.ui
 
 drupal.shortcut:
   version: VERSION
@@ -274,6 +277,11 @@ progress:
     component:
       css/components/progress.css: {}
 
+tabbingmanager:
+  version: VERSION
+  dependencies:
+    - claro/claro.jquery.ui
+
 filter:
   version: VERSION
   css:
diff --git a/core/themes/claro/css/base/variables.css b/core/themes/claro/css/base/variables.css
index 14df9b551c6785849072c4a1583ce56105695d47..327c8b9ba971037df6bc152b63df99fec0f9086b 100644
--- a/core/themes/claro/css/base/variables.css
+++ b/core/themes/claro/css/base/variables.css
@@ -249,26 +249,4 @@
    * Breadcrumb.
    */
   --breadcrumb-height: 1.25rem;
-  /**
-    * Vertical Tabs.
-    */
-  --vertical-tabs-margin-vertical: var(--space-s);
-  --vertical-tabs-border-radius: var(--details-accordion-border-size-radius);
-  --vertical-tabs-shadow: var(--details-box-shadow);
-  --vertical-tabs-border-color: var(--details-border-color);
-  --vertical-tabs-border-size: 1px;
-  --vertical-tabs-border: var(--vertical-tabs-border-size) solid var(--vertical-tabs-border-color);
-  --vertical-tabs-menu-item-shadow-extraspace: 0.5rem;
-  --vertical-tabs-menu-item--top-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -2);
-  --vertical-tabs-menu-item--right-margin: calc(var(--vertical-tabs-border-size) * -1);
-  --vertical-tabs-menu-item--bottom-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -1);
-  --vertical-tabs-menu-item--left-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -1);
-  --vertical-tabs-menu-separator-color: var(--color-gray-200);
-  --vertical-tabs-menu-separator-size: 1px;
-  --vertical-tabs-menu-width: 20em;
-  --vertical-tabs-pane-width: calc(100% - var(--vertical-tabs-menu-width));
-  --vertical-tabs-menu-link-focus-border-size: var(--details-summary-focus-border-size);
-  --vertical-tabs-menu-link--active-border-size: 0.25rem;
-  --vertical-tabs-menu-link--active-border-color: var(--color-absolutezero);
-  --vertical-tabs-menu--z-index: 0;
 }
diff --git a/core/themes/claro/css/base/variables.pcss.css b/core/themes/claro/css/base/variables.pcss.css
index 9a17cf871058c293794c4f88e891e59c7f44647f..0db1ec674afea277a764f0aacac94ba561642cf0 100644
--- a/core/themes/claro/css/base/variables.pcss.css
+++ b/core/themes/claro/css/base/variables.pcss.css
@@ -243,26 +243,4 @@
    * Breadcrumb.
    */
   --breadcrumb-height: 1.25rem;
-  /**
-    * Vertical Tabs.
-    */
-  --vertical-tabs-margin-vertical: var(--space-s);
-  --vertical-tabs-border-radius: var(--details-accordion-border-size-radius);
-  --vertical-tabs-shadow: var(--details-box-shadow);
-  --vertical-tabs-border-color: var(--details-border-color);
-  --vertical-tabs-border-size: 1px;
-  --vertical-tabs-border: var(--vertical-tabs-border-size) solid var(--vertical-tabs-border-color);
-  --vertical-tabs-menu-item-shadow-extraspace: 0.5rem;
-  --vertical-tabs-menu-item--top-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -2);
-  --vertical-tabs-menu-item--right-margin: calc(var(--vertical-tabs-border-size) * -1);
-  --vertical-tabs-menu-item--bottom-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -1);
-  --vertical-tabs-menu-item--left-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -1);
-  --vertical-tabs-menu-separator-color: var(--color-gray-200);
-  --vertical-tabs-menu-separator-size: 1px;
-  --vertical-tabs-menu-width: 20em;
-  --vertical-tabs-pane-width: calc(100% - var(--vertical-tabs-menu-width));
-  --vertical-tabs-menu-link-focus-border-size: var(--details-summary-focus-border-size);
-  --vertical-tabs-menu-link--active-border-size: 4px;
-  --vertical-tabs-menu-link--active-border-color: var(--color-absolutezero);
-  --vertical-tabs-menu--z-index: 0;
 }
diff --git a/core/themes/claro/css/components/form.css b/core/themes/claro/css/components/form.css
index 4f4edd8ab58463baabdf2095556d28365d5f2af8..a64807609fec8795badf383ff292a5eb2da9fb1c 100644
--- a/core/themes/claro/css/components/form.css
+++ b/core/themes/claro/css/components/form.css
@@ -20,8 +20,7 @@
  */
 
 .form-item {
-  margin-top: var(--space-l);
-  margin-bottom: var(--space-l);
+  margin-block: var(--space-l);
 }
 
 /**
@@ -32,8 +31,7 @@
 
 tr .form-item,
 .container-inline .form-item {
-  margin-top: var(--space-s);
-  margin-bottom: var(--space-s);
+  margin-block: var(--space-s);
 }
 
 /**
@@ -42,26 +40,12 @@ tr .form-item,
 
 .form-item__label {
   display: table;
-  margin-top: calc(var(--space-xs) / 2); /* 4px */
-  margin-bottom: calc(var(--space-xs) / 2); /* 4px */
+  margin-block: calc(var(--space-xs) / 2); /* 4px */
   font-size: var(--font-size-s); /* ~14px */
   font-weight: bold;
   line-height: var(--line-height-form-label);
 }
 
-/* Multiple selectors used to ensure styling even if modules override markup. */
-
-.form-item__label--multiple-value-form,
-.field-multiple-table .field-label h4.label {
-  display: inline-block;
-  margin-top: 0;
-  margin-bottom: 0;
-  vertical-align: middle;
-  font-size: inherit;
-  font-weight: inherit;
-  line-height: inherit;
-}
-
 .form-item__label[for] {
   cursor: pointer;
 }
@@ -71,8 +55,6 @@ tr .form-item,
   font-weight: normal;
 }
 
-/* Label states. */
-
 .form-item__label.has-error {
   color: var(--input--error-color);
 }
@@ -86,11 +68,22 @@ tr .form-item,
   color: var(--input--disabled-fg-color);
 }
 
+/* Multiple selectors used to ensure styling even if modules override markup. */
+
+.form-item__label--multiple-value-form,
+.field-multiple-table .field-label h4.label {
+  display: inline-block;
+  margin-block: 0;
+  align-self: center;
+  font-size: inherit;
+  font-weight: inherit;
+  line-height: inherit;
+}
+
 .form-item__label.form-required::after,
 .fieldset__label.form-required::after {
   display: inline-block;
-  margin-right: 0.15em;
-  margin-left: 0.15em;
+  margin-inline: 0.15em;
   content: "*";
   color: var(--color-maximumred);
   font-size: 0.875rem;
@@ -101,15 +94,12 @@ tr .form-item,
  */
 
 .form-item__description {
-  margin-top: calc(6rem / 16); /* 6px */
-  margin-bottom: calc(6rem / 16); /* 6px */
+  margin-block: calc(6rem / 16); /* 6px */
   color: var(--input-fg-color--description);
   font-size: var(--font-size-xs); /* ~13px */
   line-height: calc(17rem / 16); /* 17px */
 }
 
-/* Description states. */
-
 .form-item__description.is-disabled {
   color: var(--input--disabled-fg-color);
 }
@@ -119,8 +109,7 @@ tr .form-item,
  */
 
 .form-item__error-message {
-  margin-top: calc(6rem / 16); /* 6px */
-  margin-bottom: calc(6rem / 16); /* 6px */
+  margin-block: calc(6rem / 16); /* 6px */
   color: var(--input--error-color);
   font-size: var(--font-size-xs); /* ~13px */
   font-weight: normal;
@@ -136,12 +125,7 @@ tr .form-item,
 
 @media screen and (min-width: 37.5625rem) {
   .form-item__suffix {
-    margin-left: var(--space-xs); /* LTR */
-  }
-
-  [dir="rtl"] .form-item__suffix {
-    margin-right: var(--space-xs);
-    margin-left: 0;
+    margin-inline-start: var(--space-xs);
   }
 }
 
@@ -153,14 +137,12 @@ tr .form-item,
   display: flex;
   flex-wrap: wrap;
   align-items: flex-start;
-  margin-top: var(--space-m);
-  margin-bottom: var(--space-m);
+  margin-block: var(--space-m);
 }
 
 .form-actions .button,
 .form-actions .action-link {
-  margin-top: var(--space-m);
-  margin-bottom: var(--space-m);
+  margin-block: var(--space-m);
 }
 
 .form-actions .ajax-progress--throbber {
@@ -205,15 +187,15 @@ tr .form-item,
 
 .form-item--editor-format .form-item__label,
 .form-item--editor-format .form-item__prefix,
-.form-item--editor-format .form-item__suffix {
-  margin-right: var(--space-xs); /* LTR */
+.form-item--editor-format .form-item__suffix,
+.form-item--editor-format .form-element--editor-format {
+  min-width: 1px;
 }
 
-[dir="rtl"] .form-item--editor-format .form-item__label,
-[dir="rtl"] .form-item--editor-format .form-item__prefix,
-[dir="rtl"] .form-item--editor-format .form-item__suffix {
-  margin-right: 0;
-  margin-left: var(--space-xs);
+.form-item--editor-format .form-item__label,
+.form-item--editor-format .form-item__prefix,
+.form-item--editor-format .form-item__suffix {
+  margin-inline-end: var(--space-xs);
 }
 
 .form-item--editor-format .form-item__description,
diff --git a/core/themes/claro/css/components/form.pcss.css b/core/themes/claro/css/components/form.pcss.css
index 70f0c6574cef76e0efcdf0c2d36fbf59ffb1f920..20a7282358002d8b6d1f8ea210c109e1ab99f5ff 100644
--- a/core/themes/claro/css/components/form.pcss.css
+++ b/core/themes/claro/css/components/form.pcss.css
@@ -12,8 +12,7 @@
  * General form item.
  */
 .form-item {
-  margin-top: var(--space-l);
-  margin-bottom: var(--space-l);
+  margin-block: var(--space-l);
 }
 /**
  * When a table row or a container-inline has a single form item, prevent it
@@ -22,8 +21,7 @@
  */
 tr .form-item,
 .container-inline .form-item {
-  margin-top: var(--space-s);
-  margin-bottom: var(--space-s);
+  margin-block: var(--space-s);
 }
 
 /**
@@ -31,46 +29,48 @@ tr .form-item,
  */
 .form-item__label {
   display: table;
-  margin-top: calc(var(--space-xs) / 2); /* 4px */
-  margin-bottom: calc(var(--space-xs) / 2); /* 4px */
+  margin-block: calc(var(--space-xs) / 2); /* 4px */
   font-size: var(--font-size-s); /* ~14px */
   font-weight: bold;
   line-height: var(--line-height-form-label);
+
+  &[for] {
+    cursor: pointer;
+  }
+
+  &.option {
+    display: inline;
+    font-weight: normal;
+  }
+
+  &.has-error {
+    color: var(--input--error-color);
+  }
+
+  &.option.has-error {
+    color: inherit;
+  }
+
+  &.is-disabled {
+    cursor: default; /* @todo ...or auto? */
+    color: var(--input--disabled-fg-color);
+  }
 }
 /* Multiple selectors used to ensure styling even if modules override markup. */
 .form-item__label--multiple-value-form,
 .field-multiple-table .field-label h4.label {
   display: inline-block;
-  margin-top: 0;
-  margin-bottom: 0;
-  vertical-align: middle;
+  margin-block: 0;
+  align-self: center;
   font-size: inherit;
   font-weight: inherit;
   line-height: inherit;
 }
-.form-item__label[for] {
-  cursor: pointer;
-}
-.form-item__label.option {
-  display: inline;
-  font-weight: normal;
-}
-/* Label states. */
-.form-item__label.has-error {
-  color: var(--input--error-color);
-}
-.form-item__label.option.has-error {
-  color: inherit;
-}
-.form-item__label.is-disabled {
-  cursor: default; /* @todo ...or auto? */
-  color: var(--input--disabled-fg-color);
-}
+
 .form-item__label.form-required::after,
 .fieldset__label.form-required::after {
   display: inline-block;
-  margin-right: 0.15em;
-  margin-left: 0.15em;
+  margin-inline: 0.15em;
   content: "*";
   color: var(--color-maximumred);
   font-size: 0.875rem;
@@ -80,23 +80,21 @@ tr .form-item,
  * Form item description.
  */
 .form-item__description {
-  margin-top: calc(6rem / 16); /* 6px */
-  margin-bottom: calc(6rem / 16); /* 6px */
+  margin-block: calc(6rem / 16); /* 6px */
   color: var(--input-fg-color--description);
   font-size: var(--font-size-xs); /* ~13px */
   line-height: calc(17rem / 16); /* 17px */
-}
-/* Description states. */
-.form-item__description.is-disabled {
-  color: var(--input--disabled-fg-color);
+
+  &.is-disabled {
+    color: var(--input--disabled-fg-color);
+  }
 }
 
 /**
  * Error message (Inline form errors).
  */
 .form-item__error-message {
-  margin-top: calc(6rem / 16); /* 6px */
-  margin-bottom: calc(6rem / 16); /* 6px */
+  margin-block: calc(6rem / 16); /* 6px */
   color: var(--input--error-color);
   font-size: var(--font-size-xs); /* ~13px */
   font-weight: normal;
@@ -111,12 +109,7 @@ tr .form-item,
 /* Add some spacing so that the focus ring and suffix don't overlap. */
 @media screen and (min-width: 601px) {
   .form-item__suffix {
-    margin-left: var(--space-xs); /* LTR */
-  }
-
-  [dir="rtl"] .form-item__suffix {
-    margin-right: var(--space-xs);
-    margin-left: 0;
+    margin-inline-start: var(--space-xs);
   }
 }
 
@@ -127,16 +120,16 @@ tr .form-item,
   display: flex;
   flex-wrap: wrap;
   align-items: flex-start;
-  margin-top: var(--space-m);
-  margin-bottom: var(--space-m);
-}
-.form-actions .button,
-.form-actions .action-link {
-  margin-top: var(--space-m);
-  margin-bottom: var(--space-m);
-}
-.form-actions .ajax-progress--throbber {
-  align-self: center;
+  margin-block: var(--space-m);
+
+  & .button,
+  & .action-link {
+    margin-block: var(--space-m);
+  }
+
+  & .ajax-progress--throbber {
+    align-self: center;
+  }
 }
 
 /**
@@ -164,31 +157,32 @@ tr .form-item,
   flex-wrap: wrap;
   align-items: center;
   max-width: 100%;
-}
 
-.form-item--editor-format .form-item__label,
-.form-item--editor-format .form-item__prefix,
-.form-item--editor-format .form-item__suffix,
-.form-item--editor-format .form-element--editor-format {
-  min-width: 1px;
-}
+  & .form-item__label,
+  & .form-item__prefix,
+  & .form-item__suffix,
+  & .form-element--editor-format {
+    min-width: 1px;
+  }
 
-.form-item--editor-format .form-item__label,
-.form-item--editor-format .form-item__prefix,
-.form-item--editor-format .form-item__suffix {
-  margin-right: var(--space-xs); /* LTR */
-}
-[dir="rtl"] .form-item--editor-format .form-item__label,
-[dir="rtl"] .form-item--editor-format .form-item__prefix,
-[dir="rtl"] .form-item--editor-format .form-item__suffix {
-  margin-right: 0;
-  margin-left: var(--space-xs);
-}
+  & .form-item__label,
+  & .form-item__prefix,
+  & .form-item__suffix,
+  & .form-element--editor-format {
+    min-width: 1px;
+  }
 
-.form-item--editor-format .form-item__description,
-.form-item--editor-format .form-item__error-message {
-  flex: 0 1 100%;
-  min-width: 1px;
+  & .form-item__label,
+  & .form-item__prefix,
+  & .form-item__suffix {
+    margin-inline-end: var(--space-xs);
+  }
+
+  & .form-item__description,
+  & .form-item__error-message {
+    flex: 0 1 100%;
+    min-width: 1px;
+  }
 }
 
 /**
diff --git a/core/themes/claro/css/components/tableselect.css b/core/themes/claro/css/components/tableselect.css
index c2b3badf7bc8c57497f4dbd9e2a931e1e79c65b3..c26d82301f9f6412b539b14b17aa59ebd15ed3eb 100644
--- a/core/themes/claro/css/components/tableselect.css
+++ b/core/themes/claro/css/components/tableselect.css
@@ -76,11 +76,11 @@ tr.selected td {
   box-shadow: none;
 }
 
-.views-bulk-actions__item .button--primary {
+.views-bulk-actions__item .button--primary:not(:disabled, .is-disabled) {
   background: var(--color-blue-400);
 }
 
-.views-bulk-actions__item .button--primary:hover {
+.views-bulk-actions__item .button--primary:not(:disabled, .is-disabled):hover {
   background: var(--color-blue-500);
 }
 
diff --git a/core/themes/claro/css/components/tableselect.pcss.css b/core/themes/claro/css/components/tableselect.pcss.css
index b808197c2295decd3d8c1c23679d8eadcc0ef201..015280fd1f3599465e01c79154efa0e251196775 100644
--- a/core/themes/claro/css/components/tableselect.pcss.css
+++ b/core/themes/claro/css/components/tableselect.pcss.css
@@ -67,7 +67,7 @@ tr.selected td {
       box-shadow: none;
     }
   }
-  & .button--primary {
+  & .button--primary:not(:disabled, .is-disabled) {
     background: var(--color-blue-400);
     &:hover {
       background: var(--color-blue-500);
diff --git a/core/themes/claro/css/components/vertical-tabs.css b/core/themes/claro/css/components/vertical-tabs.css
index 871ba2cc67af20db103a8fe406f8d7d4fe5d0757..f00e953cbb091413e5b2a5b050e33cb84ba278b5 100644
--- a/core/themes/claro/css/components/vertical-tabs.css
+++ b/core/themes/claro/css/components/vertical-tabs.css
@@ -12,15 +12,39 @@
  * Replaces /core/misc/vertical-tabs.css.
  */
 
+:root {
+  /**
+   * Vertical Tabs.
+   */
+  --vertical-tabs-margin-vertical: var(--space-s);
+  --vertical-tabs-border-radius: var(--details-accordion-border-size-radius);
+  --vertical-tabs-shadow: var(--details-box-shadow);
+  --vertical-tabs-border-color: var(--details-border-color);
+  --vertical-tabs-border-size: 1px;
+  --vertical-tabs-border: var(--vertical-tabs-border-size) solid var(--vertical-tabs-border-color);
+  --vertical-tabs-menu-item-shadow-extraspace: 0.5rem;
+  --vertical-tabs-menu-item--top-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -2);
+  --vertical-tabs-menu-item--right-margin: calc(var(--vertical-tabs-border-size) * -1);
+  --vertical-tabs-menu-item--bottom-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -1);
+  --vertical-tabs-menu-item--left-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -1);
+  --vertical-tabs-menu-separator-color: var(--color-gray-200);
+  --vertical-tabs-menu-separator-size: 1px;
+  --vertical-tabs-menu-width: 20em;
+  --vertical-tabs-pane-width: calc(100% - var(--vertical-tabs-menu-width));
+  --vertical-tabs-menu-link-focus-border-size: var(--details-summary-focus-border-size);
+  --vertical-tabs-menu-link--active-border-size: 0.25rem;
+  --vertical-tabs-menu-link--active-border-color: var(--color-absolutezero);
+  --vertical-tabs-menu--z-index: 0;
+}
+
 /**
  * Main wrapper of vertical tabs.
  * This wrapper div is added by JavaScript.
  */
 
 .vertical-tabs {
-  margin-top: var(--vertical-tabs-margin-vertical);
-  margin-bottom: var(--vertical-tabs-margin-vertical);
-  border-top: 1px solid transparent; /* Need to hide the pane wrapper clearfix's height */
+  margin-block: var(--vertical-tabs-margin-vertical);
+  border-block-start: 1px solid transparent; /* Need to hide the pane wrapper clearfix's height */
 }
 
 /**
@@ -33,14 +57,13 @@
   float: left; /* LTR */
   width: var(--vertical-tabs-menu-width);
   margin: 0;
-  padding-top: var(--vertical-tabs-menu-item-shadow-extraspace);
+  padding-block-start: var(--vertical-tabs-menu-item-shadow-extraspace);
   list-style: none;
   color: var(--color-text);
 }
 
 [dir="rtl"] .vertical-tabs__menu {
   float: right;
-  margin: 0;
 }
 
 /**
@@ -49,15 +72,10 @@
 
 .vertical-tabs__menu-item {
   overflow: hidden;
-  margin: var(--vertical-tabs-menu-item--top-margin) var(--vertical-tabs-menu-item--right-margin) var(--vertical-tabs-menu-item--bottom-margin) var(--vertical-tabs-menu-item--left-margin); /* LTR */
-  padding: var(--vertical-tabs-menu-item-shadow-extraspace) 0 var(--vertical-tabs-menu-item-shadow-extraspace) var(--vertical-tabs-menu-item-shadow-extraspace); /* LTR */
-}
-
-[dir="rtl"] .vertical-tabs__menu-item {
-  margin-right: var(--vertical-tabs-menu-item--left-margin);
-  margin-left: var(--vertical-tabs-menu-item--right-margin);
-  padding-right: var(--vertical-tabs-menu-item-shadow-extraspace);
-  padding-left: 0;
+  margin-block: var(--vertical-tabs-menu-item--top-margin);
+  margin-inline: var(--vertical-tabs-menu-item--left-margin) var(--vertical-tabs-menu-item--right-margin);
+  padding-block: var(--vertical-tabs-menu-item-shadow-extraspace);
+  padding-inline: var(--vertical-tabs-menu-item-shadow-extraspace) 0;
 }
 
 /**
@@ -71,8 +89,8 @@
   z-index: 1; /* The line should be kept above the vertical tabs menu link to keep it visible even if the link is hovered and gets the 'hover' background color. */
   display: block;
   width: 100%;
-  margin-top: calc(var(--vertical-tabs-menu-separator-size) * -1);
-  border-top: var(--vertical-tabs-menu-separator-size) solid var(--vertical-tabs-menu-separator-color);
+  margin-block-start: calc(var(--vertical-tabs-menu-separator-size) * -1);
+  border-block-start: var(--vertical-tabs-menu-separator-size) solid var(--vertical-tabs-menu-separator-color);
 }
 
 /**
@@ -123,8 +141,8 @@
 .vertical-tabs__menu-link {
   position: relative;
   display: block;
-  margin-top: calc(var(--vertical-tabs-border-size) * -1);
-  padding: var(--space-s) var(--space-s) var(--space-s) calc(var(--space-l) - var(--vertical-tabs-menu-link--active-border-size)); /* LTR */
+  margin-block-start: calc(var(--vertical-tabs-border-size) * -1);
+  padding: var(--space-s) var(--space-s) var(--space-s) calc(var(--space-l) - var(--vertical-tabs-menu-link--active-border-size));
   -webkit-text-decoration: none;
   text-decoration: none;
   word-wrap: break-word;
@@ -132,19 +150,14 @@
   hyphens: auto;
   color: var(--color-text);
   border: var(--vertical-tabs-border-size) solid transparent;
-  border-width: var(--vertical-tabs-border-size) 0 var(--vertical-tabs-border-size) var(--vertical-tabs-menu-link--active-border-size); /* LTR */
-  border-radius: var(--vertical-tabs-border-radius) 0 0 var(--vertical-tabs-border-radius); /* LTR */
+  border-block-width: var(--vertical-tabs-border-size);
+  border-inline-width: var(--vertical-tabs-menu-link--active-border-size) 0;
+  border-start-start-radius: var(--vertical-tabs-border-radius);
+  border-start-end-radius: 0;
+  border-end-end-radius: 0;
+  border-end-start-radius: var(--vertical-tabs-border-radius);
 }
 
-[dir="rtl"] .vertical-tabs__menu-link {
-  padding-right: calc(var(--space-l) - var(--vertical-tabs-menu-link--active-border-size));
-  padding-left: var(--space-s);
-  border-width: var(--vertical-tabs-border-size) var(--vertical-tabs-menu-link--active-border-size) var(--vertical-tabs-border-size) 0;
-  border-radius: 0 var(--vertical-tabs-border-radius) var(--vertical-tabs-border-radius) 0;
-}
-
-/* Menu link states. */
-
 .vertical-tabs__menu-link:focus {
   z-index: 4; /* Focus state should be on the highest level to make the focus effect be fully visible. This also means that it should have bigger z-index than the selected link. */
   -webkit-text-decoration: none;
@@ -152,46 +165,34 @@
   box-shadow: none;
 }
 
-.vertical-tabs__menu-link:hover {
-  -webkit-text-decoration: none;
-  text-decoration: none;
-  color: var(--color-absolutezero);
-}
-
-/* This pseudo element provides the background for the hover state. */
-
-.vertical-tabs__menu-link::before {
+.vertical-tabs__menu-link:focus::after {
   position: absolute;
-  z-index: 0; /* This should be on a lower level than the menu-item separator lines. */
-  top: calc(var(--vertical-tabs-border-size) * -1);
-  right: 0; /* LTR */
-  bottom: calc(var(--vertical-tabs-border-size) * -1);
-  left: calc(var(--vertical-tabs-menu-link--active-border-size) * -1); /* LTR */
+  inset: 0;
+  margin: calc(var(--vertical-tabs-border-size) * -1) calc(var(--vertical-tabs-menu-link--active-border-size) * -1);
   content: "";
   pointer-events: none;
-  background-clip: padding-box;
+  border: var(--vertical-tabs-menu-link-focus-border-size) solid var(--color-focus);
+  border-radius: var(--vertical-tabs-border-radius);
 }
 
-[dir="rtl"] .vertical-tabs__menu-link::before {
-  right: calc(var(--vertical-tabs-menu-link--active-border-size) * -1);
-  left: 0;
+.vertical-tabs__menu-link:hover {
+  -webkit-text-decoration: none;
+  text-decoration: none;
+  color: var(--color-absolutezero);
 }
 
 .vertical-tabs__menu-link:hover::before {
   background: var(--color-bgblue-hover);
 }
 
-.vertical-tabs__menu-link:focus::after {
+.vertical-tabs__menu-link::before {
   position: absolute;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  margin: calc(var(--vertical-tabs-border-size) * -1) calc(var(--vertical-tabs-menu-link--active-border-size) * -1);
+  z-index: 0; /* This should be on a lower level than the menu-item separator lines. */
+  inset-block: calc(var(--vertical-tabs-border-size) * -1);
+  inset-inline: calc(var(--vertical-tabs-menu-link--active-border-size) * -1) 0;
   content: "";
   pointer-events: none;
-  border: var(--vertical-tabs-menu-link-focus-border-size) solid var(--color-focus);
-  border-radius: var(--vertical-tabs-border-radius);
+  background-clip: padding-box;
 }
 
 .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-link {
@@ -209,14 +210,12 @@
 
 .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-link::before {
   z-index: 1; /* The blue active-tab indication should be on a higher level than the green focus border. */
-  border-left: var(--vertical-tabs-menu-link--active-border-size) solid var(--vertical-tabs-menu-link--active-border-color); /* LTR */
-  border-radius: var(--base-border-radius) 0 0 var(--base-border-radius); /* LTR */
-}
-
-[dir="rtl"] .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-link::before {
-  border-right: var(--vertical-tabs-menu-link--active-border-size) solid var(--vertical-tabs-menu-link--active-border-color);
-  border-left: 0;
-  border-radius: 0 var(--base-border-radius) var(--base-border-radius) 0;
+  border-inline-start: var(--vertical-tabs-menu-link--active-border-size) solid var(--vertical-tabs-menu-link--active-border-color);
+  border-radius: var(--base-border-radius) 0 0 var(--base-border-radius);
+  border-start-start-radius: var(--base-border-radius);
+  border-start-end-radius: 0;
+  border-end-end-radius: 0;
+  border-end-start-radius: var(--base-border-radius);
 }
 
 .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-link:hover::before {
@@ -245,8 +244,7 @@
 
 .vertical-tabs__items {
   box-sizing: border-box;
-  margin-top: var(--vertical-tabs-margin-vertical);
-  margin-bottom: var(--vertical-tabs-margin-vertical);
+  margin-block: var(--vertical-tabs-margin-vertical);
   color: var(--color-text);
   border: var(--vertical-tabs-border);
   border-radius: var(--vertical-tabs-border-radius);
@@ -259,29 +257,23 @@
 .vertical-tabs__panes {
   position: relative;
   z-index: 1; /* The wrapper of the details of the vertical tabs should be on a higher level than the vertical tabs menu */
-  top: -1px;
-  margin-top: 0;
-  margin-bottom: 0;
+  inset-block-start: -1px;
+  margin-block: 0;
 }
 
-/* This clearfix makes the pane wrapper at least as tall as the menu. */
-
 .vertical-tabs__panes::after {
-  display: block;
+  display: block; /* This clearfix makes the pane wrapper at least as tall as the menu. */
   clear: both;
   content: "";
 }
 
 .vertical-tabs .vertical-tabs__panes {
-  margin-left: var(--vertical-tabs-menu-width); /* LTR */
-  border-top-left-radius: 0; /* LTR */
+  margin-inline-start: var(--vertical-tabs-menu-width);
+  border-top-left-radius: 0;
 }
 
-[dir="rtl"] .vertical-tabs .vertical-tabs__panes {
-  margin-right: var(--vertical-tabs-menu-width);
-  margin-left: 0;
+[dir="rtl"] :is(.vertical-tabs .vertical-tabs__panes) {
   border-top-left-radius: var(--vertical-tabs-border-radius);
-  border-top-right-radius: 0;
 }
 
 /**
diff --git a/core/themes/claro/css/components/vertical-tabs.pcss.css b/core/themes/claro/css/components/vertical-tabs.pcss.css
index f391624ec34eb665def2a986df727712a354b261..c85061f70b88a0fdbfb0a09c5b3c32825bafdf99 100644
--- a/core/themes/claro/css/components/vertical-tabs.pcss.css
+++ b/core/themes/claro/css/components/vertical-tabs.pcss.css
@@ -5,14 +5,38 @@
  * Replaces /core/misc/vertical-tabs.css.
  */
 
+:root {
+  /**
+   * Vertical Tabs.
+   */
+  --vertical-tabs-margin-vertical: var(--space-s);
+  --vertical-tabs-border-radius: var(--details-accordion-border-size-radius);
+  --vertical-tabs-shadow: var(--details-box-shadow);
+  --vertical-tabs-border-color: var(--details-border-color);
+  --vertical-tabs-border-size: 1px;
+  --vertical-tabs-border: var(--vertical-tabs-border-size) solid var(--vertical-tabs-border-color);
+  --vertical-tabs-menu-item-shadow-extraspace: 0.5rem;
+  --vertical-tabs-menu-item--top-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -2);
+  --vertical-tabs-menu-item--right-margin: calc(var(--vertical-tabs-border-size) * -1);
+  --vertical-tabs-menu-item--bottom-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -1);
+  --vertical-tabs-menu-item--left-margin: calc(var(--vertical-tabs-menu-item-shadow-extraspace) * -1);
+  --vertical-tabs-menu-separator-color: var(--color-gray-200);
+  --vertical-tabs-menu-separator-size: 1px;
+  --vertical-tabs-menu-width: 20em;
+  --vertical-tabs-pane-width: calc(100% - var(--vertical-tabs-menu-width));
+  --vertical-tabs-menu-link-focus-border-size: var(--details-summary-focus-border-size);
+  --vertical-tabs-menu-link--active-border-size: 4px;
+  --vertical-tabs-menu-link--active-border-color: var(--color-absolutezero);
+  --vertical-tabs-menu--z-index: 0;
+}
+
 /**
  * Main wrapper of vertical tabs.
  * This wrapper div is added by JavaScript.
  */
 .vertical-tabs {
-  margin-top: var(--vertical-tabs-margin-vertical);
-  margin-bottom: var(--vertical-tabs-margin-vertical);
-  border-top: 1px solid transparent; /* Need to hide the pane wrapper clearfix's height */
+  margin-block: var(--vertical-tabs-margin-vertical);
+  border-block-start: 1px solid transparent; /* Need to hide the pane wrapper clearfix's height */
 }
 
 /**
@@ -24,13 +48,13 @@
   float: left; /* LTR */
   width: var(--vertical-tabs-menu-width);
   margin: 0;
-  padding-top: var(--vertical-tabs-menu-item-shadow-extraspace);
+  padding-block-start: var(--vertical-tabs-menu-item-shadow-extraspace);
   list-style: none;
   color: var(--color-text);
-}
-[dir="rtl"] .vertical-tabs__menu {
-  float: right;
-  margin: 0;
+
+  @nest [dir="rtl"] & {
+    float: right;
+  }
 }
 
 /**
@@ -38,14 +62,10 @@
  */
 .vertical-tabs__menu-item {
   overflow: hidden;
-  margin: var(--vertical-tabs-menu-item--top-margin) var(--vertical-tabs-menu-item--right-margin) var(--vertical-tabs-menu-item--bottom-margin) var(--vertical-tabs-menu-item--left-margin); /* LTR */
-  padding: var(--vertical-tabs-menu-item-shadow-extraspace) 0 var(--vertical-tabs-menu-item-shadow-extraspace) var(--vertical-tabs-menu-item-shadow-extraspace); /* LTR */
-}
-[dir="rtl"] .vertical-tabs__menu-item {
-  margin-right: var(--vertical-tabs-menu-item--left-margin);
-  margin-left: var(--vertical-tabs-menu-item--right-margin);
-  padding-right: var(--vertical-tabs-menu-item-shadow-extraspace);
-  padding-left: 0;
+  margin-block: var(--vertical-tabs-menu-item--top-margin);
+  margin-inline: var(--vertical-tabs-menu-item--left-margin) var(--vertical-tabs-menu-item--right-margin);
+  padding-block: var(--vertical-tabs-menu-item-shadow-extraspace);
+  padding-inline: var(--vertical-tabs-menu-item-shadow-extraspace) 0;
 }
 
 /**
@@ -58,8 +78,8 @@
   z-index: 1; /* The line should be kept above the vertical tabs menu link to keep it visible even if the link is hovered and gets the 'hover' background color. */
   display: block;
   width: 100%;
-  margin-top: calc(var(--vertical-tabs-menu-separator-size) * -1);
-  border-top: var(--vertical-tabs-menu-separator-size) solid var(--vertical-tabs-menu-separator-color);
+  margin-block-start: calc(var(--vertical-tabs-menu-separator-size) * -1);
+  border-block-start: var(--vertical-tabs-menu-separator-size) solid var(--vertical-tabs-menu-separator-color);
 }
 
 /**
@@ -105,67 +125,53 @@
 .vertical-tabs__menu-link {
   position: relative;
   display: block;
-  margin-top: calc(var(--vertical-tabs-border-size) * -1);
-  padding: var(--space-s) var(--space-s) var(--space-s) calc(var(--space-l) - var(--vertical-tabs-menu-link--active-border-size)); /* LTR */
+  margin-block-start: calc(var(--vertical-tabs-border-size) * -1);
+  padding: var(--space-s) var(--space-s) var(--space-s) calc(var(--space-l) - var(--vertical-tabs-menu-link--active-border-size));
   text-decoration: none;
   word-wrap: break-word;
   hyphens: auto;
   color: var(--color-text);
   border: var(--vertical-tabs-border-size) solid transparent;
-  border-width: var(--vertical-tabs-border-size) 0 var(--vertical-tabs-border-size) var(--vertical-tabs-menu-link--active-border-size); /* LTR */
-  border-radius: var(--vertical-tabs-border-radius) 0 0 var(--vertical-tabs-border-radius); /* LTR */
-}
-[dir="rtl"] .vertical-tabs__menu-link {
-  padding-right: calc(var(--space-l) - var(--vertical-tabs-menu-link--active-border-size));
-  padding-left: var(--space-s);
-  border-width: var(--vertical-tabs-border-size) var(--vertical-tabs-menu-link--active-border-size) var(--vertical-tabs-border-size) 0;
-  border-radius: 0 var(--vertical-tabs-border-radius) var(--vertical-tabs-border-radius) 0;
-}
+  border-block-width: var(--vertical-tabs-border-size);
+  border-inline-width: var(--vertical-tabs-menu-link--active-border-size) 0;
+  border-start-start-radius: var(--vertical-tabs-border-radius);
+  border-start-end-radius: 0;
+  border-end-end-radius: 0;
+  border-end-start-radius: var(--vertical-tabs-border-radius);
 
-/* Menu link states. */
-.vertical-tabs__menu-link:focus {
-  z-index: 4; /* Focus state should be on the highest level to make the focus effect be fully visible. This also means that it should have bigger z-index than the selected link. */
-  text-decoration: none;
-  box-shadow: none;
-}
-
-.vertical-tabs__menu-link:hover {
-  text-decoration: none;
-  color: var(--color-absolutezero);
-}
+  &:focus {
+    z-index: 4; /* Focus state should be on the highest level to make the focus effect be fully visible. This also means that it should have bigger z-index than the selected link. */
+    text-decoration: none;
+    box-shadow: none;
 
-/* This pseudo element provides the background for the hover state. */
-.vertical-tabs__menu-link::before {
-  position: absolute;
-  z-index: 0; /* This should be on a lower level than the menu-item separator lines. */
-  top: calc(var(--vertical-tabs-border-size) * -1);
-  right: 0; /* LTR */
-  bottom: calc(var(--vertical-tabs-border-size) * -1);
-  left: calc(var(--vertical-tabs-menu-link--active-border-size) * -1); /* LTR */
-  content: "";
-  pointer-events: none;
-  background-clip: padding-box;
-}
-[dir="rtl"] .vertical-tabs__menu-link::before {
-  right: calc(var(--vertical-tabs-menu-link--active-border-size) * -1);
-  left: 0;
-}
+    &::after {
+      position: absolute;
+      inset: 0;
+      margin: calc(var(--vertical-tabs-border-size) * -1) calc(var(--vertical-tabs-menu-link--active-border-size) * -1);
+      content: "";
+      pointer-events: none;
+      border: var(--vertical-tabs-menu-link-focus-border-size) solid var(--color-focus);
+      border-radius: var(--vertical-tabs-border-radius);
+    }
+  }
 
-.vertical-tabs__menu-link:hover::before {
-  background: var(--color-bgblue-hover);
-}
+  &:hover {
+    text-decoration: none;
+    color: var(--color-absolutezero);
+    &::before {
+      background: var(--color-bgblue-hover);
+    }
+  }
 
-.vertical-tabs__menu-link:focus::after {
-  position: absolute;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  margin: calc(var(--vertical-tabs-border-size) * -1) calc(var(--vertical-tabs-menu-link--active-border-size) * -1);
-  content: "";
-  pointer-events: none;
-  border: var(--vertical-tabs-menu-link-focus-border-size) solid var(--color-focus);
-  border-radius: var(--vertical-tabs-border-radius);
+  &::before {
+    position: absolute;
+    z-index: 0; /* This should be on a lower level than the menu-item separator lines. */
+    inset-block: calc(var(--vertical-tabs-border-size) * -1);
+    inset-inline: calc(var(--vertical-tabs-menu-link--active-border-size) * -1) 0;
+    content: "";
+    pointer-events: none;
+    background-clip: padding-box;
+  }
 }
 
 .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-link {
@@ -183,13 +189,12 @@
 
 .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-link::before {
   z-index: 1; /* The blue active-tab indication should be on a higher level than the green focus border. */
-  border-left: var(--vertical-tabs-menu-link--active-border-size) solid var(--vertical-tabs-menu-link--active-border-color); /* LTR */
-  border-radius: var(--base-border-radius) 0 0 var(--base-border-radius); /* LTR */
-}
-[dir=rtl] .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-link::before {
-  border-right: var(--vertical-tabs-menu-link--active-border-size) solid var(--vertical-tabs-menu-link--active-border-color);
-  border-left: 0;
-  border-radius: 0 var(--base-border-radius) var(--base-border-radius) 0;
+  border-inline-start: var(--vertical-tabs-menu-link--active-border-size) solid var(--vertical-tabs-menu-link--active-border-color);
+  border-radius: var(--base-border-radius) 0 0 var(--base-border-radius);
+  border-start-start-radius: var(--base-border-radius);
+  border-start-end-radius: 0;
+  border-end-end-radius: 0;
+  border-end-start-radius: var(--base-border-radius);
 }
 
 .vertical-tabs__menu-item.is-selected .vertical-tabs__menu-link:hover::before {
@@ -216,8 +221,7 @@
  */
 .vertical-tabs__items {
   box-sizing: border-box;
-  margin-top: var(--vertical-tabs-margin-vertical);
-  margin-bottom: var(--vertical-tabs-margin-vertical);
+  margin-block: var(--vertical-tabs-margin-vertical);
   color: var(--color-text);
   border: var(--vertical-tabs-border);
   border-radius: var(--vertical-tabs-border-radius);
@@ -229,27 +233,23 @@
 .vertical-tabs__panes {
   position: relative;
   z-index: 1; /* The wrapper of the details of the vertical tabs should be on a higher level than the vertical tabs menu */
-  top: -1px;
-  margin-top: 0;
-  margin-bottom: 0;
-}
-/* This clearfix makes the pane wrapper at least as tall as the menu. */
-.vertical-tabs__panes::after {
-  display: block;
-  clear: both;
-  content: "";
+  inset-block-start: -1px;
+  margin-block: 0;
+
+  &::after {
+    display: block; /* This clearfix makes the pane wrapper at least as tall as the menu. */
+    clear: both;
+    content: "";
+  }
 }
 
 .vertical-tabs .vertical-tabs__panes {
-  margin-left: var(--vertical-tabs-menu-width); /* LTR */
-  border-top-left-radius: 0; /* LTR */
-}
+  margin-inline-start: var(--vertical-tabs-menu-width);
+  border-top-left-radius: 0;
 
-[dir="rtl"] .vertical-tabs .vertical-tabs__panes {
-  margin-right: var(--vertical-tabs-menu-width);
-  margin-left: 0;
-  border-top-left-radius: var(--vertical-tabs-border-radius);
-  border-top-right-radius: 0;
+  @nest [dir="rtl"] & {
+    border-top-left-radius: var(--vertical-tabs-border-radius);
+  }
 }
 
 /**
@@ -262,30 +262,30 @@
   /* Render on top of the border of vertical-tabs__items. */
   margin: calc(var(--vertical-tabs-border-size) * -1) calc(var(--vertical-tabs-border-size) * -1) 0;
   border-radius: 0;
-}
 
-.vertical-tabs__item.first {
-  border-top-left-radius: var(--details-accordion-border-size-radius);
-  border-top-right-radius: var(--details-accordion-border-size-radius);
-}
+  &.first {
+    border-top-left-radius: var(--details-accordion-border-size-radius);
+    border-top-right-radius: var(--details-accordion-border-size-radius);
+  }
 
-.vertical-tabs__item.last {
-  margin-bottom: calc(var(--vertical-tabs-border-size) * -1);
-  border-bottom-right-radius: var(--details-accordion-border-size-radius);
-  border-bottom-left-radius: var(--details-accordion-border-size-radius);
+  &.last {
+    margin-bottom: calc(var(--vertical-tabs-border-size) * -1);
+    border-bottom-right-radius: var(--details-accordion-border-size-radius);
+    border-bottom-left-radius: var(--details-accordion-border-size-radius);
+  }
 }
 
 .js .vertical-tabs .vertical-tabs__item {
   overflow: hidden;
   margin: 0;
   border: 0;
-}
 
-.js .vertical-tabs .vertical-tabs__item.first,
-.js .vertical-tabs .vertical-tabs__item.last {
-  border-radius: 0;
-}
+  &.first,
+  &.last {
+    border-radius: 0;
+  }
 
-.js .vertical-tabs .vertical-tabs__item > summary {
-  display: none;
+  & > summary {
+    display: none;
+  }
 }
diff --git a/core/themes/claro/css/state/toolbar.menu.css b/core/themes/claro/css/state/toolbar.menu.css
index 54169a9dc2a3d1747f94601b53f020398b0cb197..bc94c794cdbf12f491f1589ca720b18962d51bfd 100644
--- a/core/themes/claro/css/state/toolbar.menu.css
+++ b/core/themes/claro/css/state/toolbar.menu.css
@@ -53,6 +53,7 @@
 .toolbar .toolbar-tray .menu-item--active-trail > .toolbar-box a,
 .toolbar .toolbar-tray a.is-active {
   color: #000;
+  background-color: #f5f5f5;
   font-weight: bold;
 }
 /* ----- Toolbar menu tray for viewports less than 320px ------ */
diff --git a/core/themes/claro/css/state/toolbar.menu.pcss.css b/core/themes/claro/css/state/toolbar.menu.pcss.css
index c42ff768caeb6c3e331109c5c8ca77e6c048977a..97023b5e331ca4b533f4e1aa6a00c7f3f8b8c1f4 100644
--- a/core/themes/claro/css/state/toolbar.menu.pcss.css
+++ b/core/themes/claro/css/state/toolbar.menu.pcss.css
@@ -49,6 +49,7 @@
 .toolbar .toolbar-tray .menu-item--active-trail > .toolbar-box a,
 .toolbar .toolbar-tray a.is-active {
   color: #000;
+  background-color: #f5f5f5;
   font-weight: bold;
 }
 
diff --git a/core/themes/claro/css/theme/views_ui.admin.theme.css b/core/themes/claro/css/theme/views_ui.admin.theme.css
index cba7a9c019d8140e154317cf7127cf2fa987c068..5d51ee4430d994c202ac9e9dd3b8d9b12968a338 100644
--- a/core/themes/claro/css/theme/views_ui.admin.theme.css
+++ b/core/themes/claro/css/theme/views_ui.admin.theme.css
@@ -373,6 +373,10 @@ td.group-title {
   margin-top: 0.3125rem;
 }
 
+.view-preview-form .views-bulk-actions__item {
+  margin-block-start: 0;
+}
+
 .view-preview-form .arguments-preview {
   font-size: 1em;
 }
diff --git a/core/themes/claro/css/theme/views_ui.admin.theme.pcss.css b/core/themes/claro/css/theme/views_ui.admin.theme.pcss.css
index 7a28077a7a0d5c00eedee328473836af3f450440..4c2be1cc20ceb2095d62ae33938a82ac24c24b1c 100644
--- a/core/themes/claro/css/theme/views_ui.admin.theme.pcss.css
+++ b/core/themes/claro/css/theme/views_ui.admin.theme.pcss.css
@@ -299,6 +299,9 @@ td.group-title {
 .view-preview-form .form-actions {
   margin-top: 5px;
 }
+.view-preview-form .views-bulk-actions__item {
+  margin-block-start: 0;
+}
 .view-preview-form .arguments-preview {
   font-size: 1em;
 }
diff --git a/core/themes/claro/templates/classy/block/block--system-branding-block.html.twig b/core/themes/claro/templates/classy/block/block--system-branding-block.html.twig
index baa015b17729687d9d9d078d573e8212ae7dd409..8814363c528ade6a1d2cd99c3329f41958323578 100644
--- a/core/themes/claro/templates/classy/block/block--system-branding-block.html.twig
+++ b/core/themes/claro/templates/classy/block/block--system-branding-block.html.twig
@@ -16,7 +16,7 @@
 {% block content %}
   {% if site_logo %}
     <a href="{{ path('<front>') }}" rel="home" class="site-logo">
-      <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" />
+      <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" fetchpriority="high" />
     </a>
   {% endif %}
   {% if site_name %}
diff --git a/core/themes/olivero/css/components/feed.css b/core/themes/olivero/css/components/feed.css
index 1fb0c963755d092a3b6966350b9ded0e67f07ad5..a0582022c30a421bb244a66042fe2ad39bc78dbf 100644
--- a/core/themes/olivero/css/components/feed.css
+++ b/core/themes/olivero/css/components/feed.css
@@ -22,6 +22,13 @@
   color: var(--color--primary-50);
 }
 
+@media (max-width: 75rem) {
+  .feed-icon {
+    flex-direction: row-reverse;
+    justify-content: flex-end;
+  }
+}
+
 .feed-icon__label {
   flex-shrink: 0;
   letter-spacing: 0.08em;
@@ -38,6 +45,7 @@
   height: var(--sp1-5);
   margin-inline-start: var(--sp0-5);
   color: var(--color--white);
+  border-radius: 2px;
   background-color: var(--color--primary-50);
 }
 
@@ -45,3 +53,9 @@
   vertical-align: top;
   fill: currentColor;
 }
+
+@media (max-width: 75rem) {
+  .feed-icon__icon {
+    margin-inline: 0 var(--sp0-5);
+  }
+}
diff --git a/core/themes/olivero/css/components/feed.pcss.css b/core/themes/olivero/css/components/feed.pcss.css
index 1229c7f8ef2137fd2b2ac2f22dd05e497a672dd5..4b13c8873265da9987adf8f7631892bdbdfc15aa 100644
--- a/core/themes/olivero/css/components/feed.pcss.css
+++ b/core/themes/olivero/css/components/feed.pcss.css
@@ -14,6 +14,11 @@
   &:hover {
     color: var(--color--primary-50);
   }
+
+  @media (--max-nav) {
+    flex-direction: row-reverse;
+    justify-content: flex-end;
+  }
 }
 
 .feed-icon__label {
@@ -32,10 +37,15 @@
   height: var(--sp1-5);
   margin-inline-start: var(--sp0-5);
   color: var(--color--white);
+  border-radius: 2px;
   background-color: var(--color--primary-50);
 
   & svg {
     vertical-align: top;
     fill: currentColor;
   }
+
+  @media (--max-nav) {
+    margin-inline: 0 var(--sp0-5);
+  }
 }
diff --git a/core/themes/olivero/templates/block/block--system-branding-block.html.twig b/core/themes/olivero/templates/block/block--system-branding-block.html.twig
index 41e92c0ed4d5fa15ef3697514f508f449e7bf5bf..fc23d43c4a83970ee0dec666c3303edb81e5e025 100644
--- a/core/themes/olivero/templates/block/block--system-branding-block.html.twig
+++ b/core/themes/olivero/templates/block/block--system-branding-block.html.twig
@@ -18,7 +18,7 @@
   <div class="site-branding__inner">
     {% if site_logo %}
       <a href="{{ path('<front>') }}" rel="home" class="site-branding__logo">
-        <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" />
+        <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" fetchpriority="high" />
       </a>
     {% endif %}
     {% if site_name %}
diff --git a/core/themes/stable9/css/toolbar/toolbar.menu.css b/core/themes/stable9/css/toolbar/toolbar.menu.css
index e35d0adc2dfda05da6798f1a324660e93f19ad31..c5a08ed08e5b3ec809ba8dd25a454f8ff5c5e338 100644
--- a/core/themes/stable9/css/toolbar/toolbar.menu.css
+++ b/core/themes/stable9/css/toolbar/toolbar.menu.css
@@ -46,6 +46,7 @@
 .toolbar .toolbar-tray .menu-item--active-trail > .toolbar-box a,
 .toolbar .toolbar-tray a.is-active {
   color: #000;
+  background-color: #f5f5f5;
   font-weight: bold;
 }
 
diff --git a/core/themes/stable9/templates/block/block--system-branding-block.html.twig b/core/themes/stable9/templates/block/block--system-branding-block.html.twig
index 1f8fb9c395a67f5e7952b652f7a11e3023fde5ca..84eeced681b55eeb442fe39facd73ba470e309b1 100644
--- a/core/themes/stable9/templates/block/block--system-branding-block.html.twig
+++ b/core/themes/stable9/templates/block/block--system-branding-block.html.twig
@@ -16,7 +16,7 @@
 {% block content %}
   {% if site_logo %}
     <a href="{{ path('<front>') }}" rel="home">
-      <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" />
+      <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" fetchpriority="high" />
     </a>
   {% endif %}
   {% if site_name %}
diff --git a/core/themes/stable9/templates/form/form-element.html.twig b/core/themes/stable9/templates/form/form-element.html.twig
index 9e87a1b6da3d6f50ae9023d31f3d89ea4babb821..a13ff8ca43d4e635e51b6f38d32304e2eb843581 100644
--- a/core/themes/stable9/templates/form/form-element.html.twig
+++ b/core/themes/stable9/templates/form/form-element.html.twig
@@ -48,6 +48,7 @@
   set classes = [
     'js-form-item',
     'form-item',
+    'form-type-' ~ type|clean_class,
     'js-form-type-' ~ type|clean_class,
     'form-item-' ~ name|clean_class,
     'js-form-item-' ~ name|clean_class,
diff --git a/core/themes/starterkit_theme/templates/block/block--system-branding-block.html.twig b/core/themes/starterkit_theme/templates/block/block--system-branding-block.html.twig
index baa015b17729687d9d9d078d573e8212ae7dd409..8814363c528ade6a1d2cd99c3329f41958323578 100644
--- a/core/themes/starterkit_theme/templates/block/block--system-branding-block.html.twig
+++ b/core/themes/starterkit_theme/templates/block/block--system-branding-block.html.twig
@@ -16,7 +16,7 @@
 {% block content %}
   {% if site_logo %}
     <a href="{{ path('<front>') }}" rel="home" class="site-logo">
-      <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" />
+      <img src="{{ site_logo }}" alt="{{ 'Home'|t }}" fetchpriority="high" />
     </a>
   {% endif %}
   {% if site_name %}
diff --git a/core/yarn.lock b/core/yarn.lock
index 34b3869f6d83d7f845a1909f31fb9fe3fddf3abf..d0c58c4457d64c6764ddcec324fcbf2f76a05d3a 100644
--- a/core/yarn.lock
+++ b/core/yarn.lock
@@ -1356,6 +1356,13 @@ anymatch@~3.1.2:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
+argparse@^1.0.7:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+  integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+  dependencies:
+    sprintf-js "~1.0.2"
+
 argparse@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -2345,6 +2352,14 @@ eslint-config-prettier@^8.4.0:
   resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348"
   integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==
 
+eslint-formatter-gitlab@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-formatter-gitlab/-/eslint-formatter-gitlab-5.0.0.tgz#321989900adb17d2d31164abba62d3c2b43f6807"
+  integrity sha512-4mF/Bam1xAzsNNYE7kWN/qBQD4mSMkDBEw5AoMLY4oU5iY7BxTxcAzs253FNnUKc5FvduD4Q4+/RW9l1xjz7Zg==
+  dependencies:
+    chalk "^4.0.0"
+    yaml "^2.0.0"
+
 eslint-import-resolver-node@^0.3.7:
   version "0.3.7"
   resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7"
@@ -2479,7 +2494,7 @@ espree@^9.6.0:
     acorn-jsx "^5.3.2"
     eslint-visitor-keys "^3.4.1"
 
-esprima@^4.0.1:
+esprima@^4.0.0, esprima@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
   integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
@@ -2651,6 +2666,15 @@ fs-extra@^10.1.0:
     jsonfile "^6.0.1"
     universalify "^2.0.0"
 
+fs-extra@^8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
+  integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
+  dependencies:
+    graceful-fs "^4.2.0"
+    jsonfile "^4.0.0"
+    universalify "^0.1.0"
+
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2691,10 +2715,10 @@ get-caller-file@^2.0.5:
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
   integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
 
-get-func-name@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
-  integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==
+get-func-name@^2.0.0, get-func-name@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41"
+  integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==
 
 get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1:
   version "1.2.1"
@@ -3323,6 +3347,14 @@ js-yaml@4.1.0, js-yaml@^4.1.0:
   dependencies:
     argparse "^2.0.1"
 
+js-yaml@^3.13.1:
+  version "3.14.1"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
+  integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
 jsdom@^22.1.0:
   version "22.1.0"
   resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8"
@@ -3379,6 +3411,13 @@ json5@^1.0.2:
   dependencies:
     minimist "^1.2.0"
 
+jsonfile@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+  integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 jsonfile@^6.0.1:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
@@ -4853,6 +4892,11 @@ spdx-license-ids@^3.0.0:
   resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5"
   integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==
 
+sprintf-js@~1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+  integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+
 stacktrace-parser@0.1.10:
   version "0.1.10"
   resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a"
@@ -4958,6 +5002,15 @@ stylelint-config-standard@^33.0.0:
   dependencies:
     stylelint-config-recommended "^12.0.0"
 
+stylelint-formatter-gitlab@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/stylelint-formatter-gitlab/-/stylelint-formatter-gitlab-1.0.2.tgz#56d3f68c1b3169ecddcdf039910b678bfd97ccd5"
+  integrity sha512-Iu5NjHSp/WdhUeICuUKNi6QvpEut5KPwnrx4XU5q1GZEcAvpyqbjBq7yMAEX6850BUza45ARpsfX4yfOcnFWLQ==
+  dependencies:
+    fs-extra "^8.1.0"
+    is-glob "^4.0.1"
+    js-yaml "^3.13.1"
+
 stylelint-order@^6.0.3:
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/stylelint-order/-/stylelint-order-6.0.3.tgz#160b78650bd90463241b992581efee7159baefc2"
@@ -5257,6 +5310,11 @@ unique-string@^2.0.0:
   dependencies:
     crypto-random-string "^2.0.0"
 
+universalify@^0.1.0:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+  integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
 universalify@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"