From d038714421b225e9c7a69bca95244e051933117b Mon Sep 17 00:00:00 2001
From: Andreas Fernandez <a.fernandez@scripting-base.de>
Date: Sat, 7 Jan 2017 20:14:37 +0100
Subject: [PATCH] [!!!][FEATURE] Allow reloading of backend topbar
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

A new API is introduced that allows to reload the backend’s topbar.
The reload may be triggered via JavaScript and PHP.

As the registered events of the toolbar items within the topbar get lost
after reloading, the event registration for these toolbar items needs
some adoption.

The topbar is now reloaded in case of:
- updating the user's avatar
- after configuring an extension
- opening the EXT:belog module from the System Information menu

Resolves: #79196
Releases: master
Change-Id: Ib6b65d7327c9db2b818ad9ad549cb2f2f00d1595
Reviewed-on: https://review.typo3.org/51183
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
---
 .../Classes/Controller/BackendController.php  | 99 ++++++++++++-------
 .../Classes/Utility/BackendUtility.php        |  7 ++
 .../Configuration/Backend/AjaxRoutes.php      |  4 +
 .../Private/Partials/Backend/Topbar.html      | 37 +++++++
 .../Private/Templates/Backend/Main.html       | 40 +-------
 .../Resources/Public/JavaScript/LiveSearch.js |  9 +-
 .../JavaScript/Toolbar/ClearCacheMenu.js      |  9 +-
 .../Public/JavaScript/Toolbar/ShortcutMenu.js |  7 +-
 .../Toolbar/SystemInformationMenu.js          | 24 +++--
 .../Resources/Public/JavaScript/Viewport.js   | 15 +++
 ...-79196-ToolbarItemEventHandlingChanged.rst | 45 +++++++++
 .../Feature-79196-AllowReloadOfTopbar.rst     | 51 ++++++++++
 .../Classes/Controller/AbstractController.php |  3 +
 .../Classes/Controller/ActionController.php   |  5 +-
 .../Controller/ConfigurationController.php    | 13 ++-
 .../UploadExtensionFileController.php         |  5 +-
 .../ViewHelpers/Be/TriggerViewHelper.php      |  7 ++
 .../Public/JavaScript/Toolbar/OpendocsMenu.js |  8 +-
 .../Controller/SetupModuleController.php      |  2 +
 .../JavaScript/Toolbar/WorkspacesMenu.js      |  4 +-
 20 files changed, 298 insertions(+), 96 deletions(-)
 create mode 100644 typo3/sysext/backend/Resources/Private/Partials/Backend/Topbar.html
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Breaking-79196-ToolbarItemEventHandlingChanged.rst
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-79196-AllowReloadOfTopbar.rst

diff --git a/typo3/sysext/backend/Classes/Controller/BackendController.php b/typo3/sysext/backend/Classes/Controller/BackendController.php
index 5adbfed76341..fb5c0fcf87d8 100644
--- a/typo3/sysext/backend/Classes/Controller/BackendController.php
+++ b/typo3/sysext/backend/Classes/Controller/BackendController.php
@@ -75,6 +75,11 @@ class BackendController
      */
     protected $templatePath = 'EXT:backend/Resources/Private/Templates/';
 
+    /**
+     * @var string
+     */
+    protected $partialPath = 'EXT:backend/Resources/Private/Partials/';
+
     /**
      * @var \TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository
      */
@@ -262,40 +267,8 @@ class BackendController
         // Prepare the scaffolding, at this point extension may still add javascript and css
         $view = $this->getFluidTemplateObject($this->templatePath . 'Backend/Main.html');
 
-        // Extension Configuration to find the TYPO3 logo in the left corner
-        $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend'], ['allowed_classes' => false]);
-        $logoPath = '';
-        if (!empty($extConf['backendLogo'])) {
-            $customBackendLogo = GeneralUtility::getFileAbsFileName($extConf['backendLogo']);
-            if (!empty($customBackendLogo)) {
-                $logoPath = $customBackendLogo;
-            }
-        }
-        // if no custom logo was set or the path is invalid, use the original one
-        if (empty($logoPath)) {
-            $logoPath = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Images/typo3_logo_orange.svg');
-            $logoWidth = 22;
-            $logoHeight = 22;
-        } else {
-            // set width/height for custom logo
-            $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $logoPath);
-            $logoWidth = $imageInfo->getWidth() ?? '22';
-            $logoHeight = $imageInfo->getHeight() ?? '22';
-
-            // High-resolution?
-            if (strpos($logoPath, '@2x.') !== false) {
-                $logoWidth /= 2;
-                $logoHeight /= 2;
-            }
-        }
-
-        $view->assign('logoUrl', PathUtility::getAbsoluteWebPath($logoPath));
-        $view->assign('logoWidth', $logoWidth);
-        $view->assign('logoHeight', $logoHeight);
-        $view->assign('applicationVersion', TYPO3_version);
-        $view->assign('siteName', $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
         $view->assign('moduleMenu', $this->generateModuleMenu());
-        $view->assign('toolbar', $this->renderToolbar());
+        $view->assign('topbar', $this->renderTopbar());
 
         /******************************************************
          * Now put the complete backend document together
@@ -342,6 +315,52 @@ class BackendController
         $this->executeHook('renderPostProcess', $hookConfiguration);
     }
 
+    /**
+     * Renders the topbar, containing the backend logo, sitename etc.
+     *
+     * @return string
+     */
+    protected function renderTopbar()
+    {
+        $view = $this->getFluidTemplateObject($this->partialPath . 'Backend/Topbar.html');
+
+        // Extension Configuration to find the TYPO3 logo in the left corner
+        $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend'], ['allowed_classes' => false]);
+        $logoPath = '';
+        if (!empty($extConf['backendLogo'])) {
+            $customBackendLogo = GeneralUtility::getFileAbsFileName($extConf['backendLogo']);
+            if (!empty($customBackendLogo)) {
+                $logoPath = $customBackendLogo;
+            }
+        }
+        // if no custom logo was set or the path is invalid, use the original one
+        if (empty($logoPath)) {
+            $logoPath = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Images/typo3_logo_orange.svg');
+            $logoWidth = 22;
+            $logoHeight = 22;
+        } else {
+            // set width/height for custom logo
+            $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $logoPath);
+            $logoWidth = $imageInfo->getWidth() ?? '22';
+            $logoHeight = $imageInfo->getHeight() ?? '22';
+
+            // High-resolution?
+            if (strpos($logoPath, '@2x.') !== false) {
+                $logoWidth /= 2;
+                $logoHeight /= 2;
+            }
+        }
+
+        $view->assign('logoUrl', PathUtility::getAbsoluteWebPath($logoPath));
+        $view->assign('logoWidth', $logoWidth);
+        $view->assign('logoHeight', $logoHeight);
+        $view->assign('applicationVersion', TYPO3_version);
+        $view->assign('siteName', $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
+        $view->assign('toolbar', $this->renderToolbar());
+
+        return $view->render();
+    }
+
     /**
      * Loads the css and javascript files of all registered navigation widgets
      *
@@ -836,6 +855,19 @@ class BackendController
         return $response;
     }
 
+    /**
+     * Returns the toolbar for the AJAX request
+     *
+     * @param ServerRequestInterface $request
+     * @param ResponseInterface $response
+     * @return ResponseInterface
+     */
+    public function getTopbar(ServerRequestInterface $request, ResponseInterface $response)
+    {
+        $response->getBody()->write(json_encode(['topbar' => $this->renderTopbar()]));
+        return $response;
+    }
+
     /**
      * returns a new standalone view, shorthand function
      *
@@ -845,6 +877,7 @@ class BackendController
     protected function getFluidTemplateObject($templatePathAndFileName = null)
     {
         $view = GeneralUtility::makeInstance(StandaloneView::class);
+        $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
         if ($templatePathAndFileName) {
             $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
         }
diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
index f52e8b075b05..e7b4dc2d132d 100644
--- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php
+++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
@@ -3533,6 +3533,13 @@ class BackendUtility
 								if (top && top.TYPO3.ModuleMenu && top.TYPO3.ModuleMenu.App) {
 									top.TYPO3.ModuleMenu.App.refreshMenu();
 								}';
+                        break;
+                    case 'updateTopbar':
+                        $signals[] = '
+								if (top && top.TYPO3.Backend && top.TYPO3.Backend.Topbar) {
+									top.TYPO3.Backend.Topbar.refresh();
+								}';
+                        break;
                 }
             }
         }
diff --git a/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php b/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
index 2ee8bd2ff88b..b8a874752141 100644
--- a/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
+++ b/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
@@ -111,6 +111,10 @@ return [
         'path' => '/module-menu',
         'target' => Controller\BackendController::class . '::getModuleMenu'
     ],
+    'topbar' => [
+        'path' => '/topbar',
+        'target' => Controller\BackendController::class . '::getTopbar'
+    ],
 
     // Log in into backend
     'login' => [
diff --git a/typo3/sysext/backend/Resources/Private/Partials/Backend/Topbar.html b/typo3/sysext/backend/Resources/Private/Partials/Backend/Topbar.html
new file mode 100644
index 000000000000..74ccfe209069
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Partials/Backend/Topbar.html
@@ -0,0 +1,37 @@
+{namespace core = TYPO3\CMS\Core\ViewHelpers}
+<div class="scaffold-topbar t3js-scaffold-topbar">
+	<div class="topbar">
+		<div class="topbar-header t3js-topbar-header">
+			<button class="topbar-button topbar-button-modulemenu t3js-topbar-button-modulemenu">
+				<core:icon identifier="actions-move-move" alternativeMarkupIdentifier="inline" />
+			</button>
+			<button class="topbar-button topbar-button-navigationcomponent t3js-topbar-button-navigationcomponent">
+				<core:icon identifier="apps-pagetree-category-collapse-all" alternativeMarkupIdentifier="inline" />
+			</button>
+			<div class="topbar-header-site">
+				<a href="./" target="_top" title="{siteName} - {applicationVersion}">
+					<span class="topbar-header-site-logo">
+						<img src="{logoUrl}" width="{logoWidth}" height="{logoHeight}" title="TYPO3 Content Management System" alt="" />
+					</span>
+					<span class="topbar-header-site-title">
+						<span class="topbar-header-site-name">{siteName}</span>
+						<span class="topbar-header-site-version">{applicationVersion}</span>
+					</span>
+				</a>
+			</div>
+			<button class="topbar-button topbar-button-toolbar t3js-topbar-button-toolbar">
+				<core:icon identifier="actions-system-extension-configure" alternativeMarkupIdentifier="inline" />
+			</button>
+			<button class="topbar-button topbar-button-search t3js-topbar-button-search">
+				<core:icon identifier="actions-search" alternativeMarkupIdentifier="inline" />
+			</button>
+		</div>
+	</div>
+</div>
+<div class="scaffold-toolbar t3js-scaffold-toolbar">
+	<div class="toolbar t3js-topbar-toolbar">
+		<ul class="toolbar-list" data-typo3-role="typo3-module-menu">
+			<f:format.raw>{toolbar}</f:format.raw>
+		</ul>
+	</div>
+</div>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Private/Templates/Backend/Main.html b/typo3/sysext/backend/Resources/Private/Templates/Backend/Main.html
index 4e9132bdd84c..3f8f8d1bbc1a 100644
--- a/typo3/sysext/backend/Resources/Private/Templates/Backend/Main.html
+++ b/typo3/sysext/backend/Resources/Private/Templates/Backend/Main.html
@@ -1,48 +1,12 @@
-{namespace core = TYPO3\CMS\Core\ViewHelpers}
 <div class="scaffold t3js-scaffold scaffold-modulemenu-expanded">
-	<div class="scaffold-topbar t3js-scaffold-topbar">
-
-		<div class="topbar">
-			<div class="topbar-header t3js-topbar-header">
-				<button class="topbar-button topbar-button-modulemenu t3js-topbar-button-modulemenu">
-					<core:icon identifier="actions-move-move" alternativeMarkupIdentifier="inline" />
-				</button>
-				<button class="topbar-button topbar-button-navigationcomponent t3js-topbar-button-navigationcomponent">
-					<core:icon identifier="apps-pagetree-category-collapse-all" alternativeMarkupIdentifier="inline" />
-				</button>
-				<div class="topbar-header-site">
-					<a href="./" target="_top" title="{siteName} - {applicationVersion}">
-						<span class="topbar-header-site-logo">
-							<img src="{logoUrl}" width="{logoWidth}" height="{logoHeight}" title="TYPO3 Content Management System" alt="" />
-						</span>
-						<span class="topbar-header-site-title">
-							<span class="topbar-header-site-name">{siteName}</span>
-							<span class="topbar-header-site-version">{applicationVersion}</span>
-						</span>
-					</a>
-				</div>
-				<button class="topbar-button topbar-button-toolbar t3js-topbar-button-toolbar">
-					<core:icon identifier="actions-system-extension-configure" alternativeMarkupIdentifier="inline" />
-				</button>
-				<button class="topbar-button topbar-button-search t3js-topbar-button-search">
-					<core:icon identifier="actions-search" alternativeMarkupIdentifier="inline" />
-				</button>
-			</div>
-		</div>
-
+	<div class="t3js-scaffold-header">
+		<f:format.raw>{topbar}</f:format.raw>
 	</div>
 	<div class="scaffold-modulemenu t3js-scaffold-modulemenu">
 		<div class="modulemenu t3js-modulemenu">
 			<f:format.raw>{moduleMenu}</f:format.raw>
 		</div>
 	</div>
-	<div class="scaffold-toolbar t3js-scaffold-toolbar">
-		<div class="toolbar t3js-topbar-toolbar">
-			<ul class="toolbar-list" data-typo3-role="typo3-module-menu">
-				<f:format.raw>{toolbar}</f:format.raw>
-			</ul>
-		</div>
-	</div>
 	<div class="scaffold-content t3js-scaffold-content">
 		<div class="scaffold-content-navigation t3js-scaffold-content-navigation">
 			<div class="scaffold-content-navigation-component" data-component="typo3-navigationIframe">
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/LiveSearch.js b/typo3/sysext/backend/Resources/Public/JavaScript/LiveSearch.js
index 32d8cee916e3..9ef0d2b5140f 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/LiveSearch.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/LiveSearch.js
@@ -16,7 +16,12 @@
  * Global search to deal with everything in the backend that is search-related
  * @exports TYPO3/CMS/Backend/LiveSearch
  */
-define(['jquery', 'jquery/autocomplete', 'TYPO3/CMS/Backend/jquery.clearable'], function ($) {
+define([
+	'jquery',
+	'TYPO3/CMS/Backend/Viewport',
+	'jquery/autocomplete',
+	'TYPO3/CMS/Backend/jquery.clearable'
+], function ($, Viewport) {
 	'use strict';
 
 	var containerSelector = '#typo3-cms-backend-backend-toolbaritems-livesearchtoolbaritem';
@@ -28,7 +33,7 @@ define(['jquery', 'jquery/autocomplete', 'TYPO3/CMS/Backend/jquery.clearable'],
 	var url = TYPO3.settings.ajaxUrls['livesearch'];
 	var category = '';
 
-	$(function() {
+	Viewport.Topbar.Toolbar.registerEvent(function() {
 		$(searchFieldSelector).autocomplete({
 			// ajax options
 			serviceUrl: url,
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ClearCacheMenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ClearCacheMenu.js
index 22558fa61e5e..66041556d73f 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ClearCacheMenu.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ClearCacheMenu.js
@@ -16,7 +16,12 @@
  * main functionality for clearing caches via the top bar
  * reloading the clear cache icon
  */
-define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Notification'], function($, Icons, Notification) {
+define([
+	'jquery',
+	'TYPO3/CMS/Backend/Icons',
+	'TYPO3/CMS/Backend/Notification',
+	'TYPO3/CMS/Backend/Viewport'
+], function($, Icons, Notification, Viewport) {
 	'use strict';
 
 	/**
@@ -76,7 +81,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Notification'],
 		});
 	};
 
-	$(ClearCacheMenu.initializeEvents);
+	Viewport.Topbar.Toolbar.registerEvent(ClearCacheMenu.initializeEvents);
 
 	return ClearCacheMenu;
 });
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js
index 91145a992219..a9da605d7ec4 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js
@@ -19,8 +19,9 @@
 define(['jquery',
 		'TYPO3/CMS/Backend/Modal',
 		'TYPO3/CMS/Backend/Icons',
-		'TYPO3/CMS/Backend/Notification'
-	], function($, Modal, Icons, Notification) {
+		'TYPO3/CMS/Backend/Notification',
+		'TYPO3/CMS/Backend/Viewport'
+	], function($, Modal, Icons, Notification, Viewport) {
 	'use strict';
 
 	/**
@@ -210,7 +211,7 @@ define(['jquery',
 		});
 	};
 
-	$(ShortcutMenu.initializeEvents);
+	Viewport.Topbar.Toolbar.registerEvent(ShortcutMenu.initializeEvents);
 
 	// expose as global object
 	TYPO3.ShortcutMenu = ShortcutMenu;
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/SystemInformationMenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/SystemInformationMenu.js
index bf03794c4110..b3bb52e8d14b 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/SystemInformationMenu.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/SystemInformationMenu.js
@@ -15,7 +15,12 @@
  * Module: TYPO3/CMS/Backend/Toolbar/SystemInformationMenu
  * System information menu handler
  */
-define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Storage'], function($, Icons, Storage) {
+define([
+	'jquery',
+	'TYPO3/CMS/Backend/Icons',
+	'TYPO3/CMS/Backend/Storage',
+	'TYPO3/CMS/Backend/Viewport'
+], function($, Icons, Storage, Viewport) {
 	'use strict';
 
 	/**
@@ -28,10 +33,8 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Storage'], funct
 			containerSelector: '#typo3-cms-backend-backend-toolbaritems-systeminformationtoolbaritem',
 			toolbarIconSelector: '.toolbar-item-icon .t3js-icon',
 			menuContainerSelector: '.dropdown-menu',
-			moduleLinks: '.t3js-systeminformation-module'
-		},
-		elements: {
-			$counter: $('.t3js-systeminformation-counter')
+			moduleLinks: '.t3js-systeminformation-module',
+			counter: '.t3js-systeminformation-counter'
 		}
 	};
 
@@ -78,14 +81,15 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Storage'], funct
 	 */
 	SystemInformationMenu.updateCounter = function() {
 		var $container = $(SystemInformationMenu.identifier.containerSelector).find(SystemInformationMenu.identifier.menuContainerSelector).find('.t3js-systeminformation-container'),
+			$counter = $(SystemInformationMenu.identifier.counter),
 			count = $container.data('count'),
 			badgeClass = $container.data('severityclass');
 
-		SystemInformationMenu.elements.$counter.text(count).toggle(parseInt(count) > 0);
-		SystemInformationMenu.elements.$counter.removeClass();
+		$counter.text(count).toggle(parseInt(count) > 0);
+		$counter.removeClass();
 
 		if (badgeClass !== '') {
-			SystemInformationMenu.elements.$counter.addClass('toolbar-item-badge badge ' + badgeClass);
+			$counter.addClass('toolbar-item-badge badge ' + badgeClass);
 		}
 	};
 
@@ -113,11 +117,11 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Storage'], funct
 		$ajax.done(function() {
 			// finally, open the module now
 			TYPO3.ModuleMenu.App.showModule(requestedModule);
-			SystemInformationMenu.updateMenu();
+			Viewport.Topbar.refresh();
 		});
 	};
 
-	$(SystemInformationMenu.updateMenu);
+	Viewport.Topbar.Toolbar.registerEvent(SystemInformationMenu.updateMenu);
 
 	return SystemInformationMenu;
 });
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js b/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js
index 797f4653ae9c..75382df1f4b4 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js
@@ -142,6 +142,21 @@ define(
 						return 0;
 					}
 				}
+			},
+			Topbar: {
+				topbarSelector: '.t3js-scaffold-header',
+				refresh: function() {
+					$.ajax(TYPO3.settings.ajaxUrls['topbar']).done(function(data) {
+						$(TYPO3.Backend.Topbar.topbarSelector).html(data.topbar);
+						$(TYPO3.Backend.Topbar.topbarSelector).trigger('t3-topbar-update');
+					});
+				},
+				Toolbar: {
+					registerEvent: function (callback) {
+						$(callback);
+						$(TYPO3.Backend.Topbar.topbarSelector).on('t3-topbar-update', callback);
+					}
+				}
 			}
 		};
 
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-79196-ToolbarItemEventHandlingChanged.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-79196-ToolbarItemEventHandlingChanged.rst
new file mode 100644
index 000000000000..96215609c757
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-79196-ToolbarItemEventHandlingChanged.rst
@@ -0,0 +1,45 @@
+.. include:: ../../Includes.txt
+
+======================================================
+Breaking: #79196 - Toolbar item event handling changed
+======================================================
+
+See :issue:`79196`
+
+Description
+===========
+
+With the introduction of the topbar reloading mechanism, the event handling of toolbar items has changed. Reason is
+that the event information gets lost, as the whole topbar is rendered from scratch after a reload.
+
+
+Impact
+======
+
+After reloading the topbar, not migrated events will not get triggered anymore.
+
+
+Affected Installations
+======================
+
+All installations with old-fashioned toolbar item registrations are affected.
+
+
+Migration
+=========
+
+In most cases it's sufficient to replace the register function with `Viewport.Topbar.Toolbar.registerEvent()`.
+
+Example:
+
+.. code-block:: javascript
+
+	define(['jquery', 'TYPO3/CMS/Backend/Viewport'], function($, Viewport) {
+		// old registration
+		$(MyAwesomeItem.doStuff)
+
+		// new registration
+		Viewport.Topbar.Toolbar.registerEvent(MyAwesomeItem.doStuff);
+	});
+
+.. index:: Backend, JavaScript
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-79196-AllowReloadOfTopbar.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-79196-AllowReloadOfTopbar.rst
new file mode 100644
index 000000000000..dc8f5e70eabc
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-79196-AllowReloadOfTopbar.rst
@@ -0,0 +1,51 @@
+.. include:: ../../Includes.txt
+
+========================================
+Feature: #79196 - Allow reload of topbar
+========================================
+
+See :issue:`79196`
+
+Description
+===========
+
+A new JavaScript API to reload the backend's topbar has been introduced to the TYPO3 Core.
+
+
+Impact
+======
+
+The toolbar reloading may be triggered on JavaScript and PHP code level. To enforce the reloading on PHP side,
+call :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::setUpdateSignal('updateTopbar')`.
+
+Reloading the topbar via JavaScript requires the following code:
+
+.. code-block:: javascript
+
+	// Either: RequireJS style
+	define(['TYPO3/CMS/Backend/Viewport'], function(Viewport) {
+		Viewport.Topbar.refresh();
+	});
+
+	// Or: old-fashioned JavaScript
+	if (top && top.TYPO3.Backend && top.TYPO3.Backend.Topbar) {
+		top.TYPO3.Backend.Topbar.refresh();
+	}';
+
+
+In case a toolbar item registers to the `load` event of the page, the registration must be changed. Reason is that the
+event information gets lost, as the whole toolbar is rendered from scratch after a reload.
+
+Example:
+
+.. code-block:: javascript
+
+	define(['jquery', 'TYPO3/CMS/Backend/Viewport'], function($, Viewport) {
+		// old registration
+		$(MyAwesomeItem.doStuff)
+
+		// new registration
+		Viewport.Topbar.Toolbar.registerEvent(MyAwesomeItem.doStuff);
+	});
+
+.. index:: Backend, JavaScript, PHP-API
\ No newline at end of file
diff --git a/typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php b/typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php
index 2ff223e3bfdc..017ac8d9dcd3 100644
--- a/typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php
+++ b/typo3/sysext/extensionmanager/Classes/Controller/AbstractController.php
@@ -21,11 +21,14 @@ class AbstractController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionControl
 {
     const TRIGGER_RefreshModuleMenu = 'refreshModuleMenu';
 
+    const TRIGGER_RefreshTopbar = 'refreshTopbar';
+
     /**
      * @var array
      */
     protected $triggerArguments = [
         self::TRIGGER_RefreshModuleMenu,
+        self::TRIGGER_RefreshTopbar
     ];
 
     /**
diff --git a/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php b/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php
index 97095620658b..fa16187010f7 100644
--- a/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php
+++ b/typo3/sysext/extensionmanager/Classes/Controller/ActionController.php
@@ -98,7 +98,10 @@ class ActionController extends AbstractController
         } catch (\TYPO3\CMS\Core\Package\Exception\PackageStatesFileNotWritableException $e) {
             $this->addFlashMessage($e->getMessage(), '', \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR);
         }
-        $this->redirect('index', 'List', null, [self::TRIGGER_RefreshModuleMenu => true]);
+        $this->redirect('index', 'List', null, [
+            self::TRIGGER_RefreshModuleMenu => true,
+            self::TRIGGER_RefreshTopbar => true
+        ]);
     }
 
     /**
diff --git a/typo3/sysext/extensionmanager/Classes/Controller/ConfigurationController.php b/typo3/sysext/extensionmanager/Classes/Controller/ConfigurationController.php
index cb511ea34e5c..9e6394874d67 100644
--- a/typo3/sysext/extensionmanager/Classes/Controller/ConfigurationController.php
+++ b/typo3/sysext/extensionmanager/Classes/Controller/ConfigurationController.php
@@ -81,6 +81,8 @@ class ConfigurationController extends AbstractModuleController
         if (!isset($extension['key'])) {
             throw new ExtensionManagerException('Extension key not found.', 1359206803);
         }
+        $this->handleTriggerArguments();
+
         $extKey = $extension['key'];
         $configuration = $this->configurationItemRepository->findByExtensionKey($extKey);
         if ($configuration) {
@@ -117,7 +119,12 @@ class ConfigurationController extends AbstractModuleController
         ) {
             $this->redirect('welcome', 'Distribution', null, ['extension' => $extension->getUid()]);
         } else {
-            $this->redirect('showConfigurationForm', null, null, ['extension' => ['key' => $extensionKey]]);
+            $this->redirect('showConfigurationForm', null, null, [
+                'extension' => [
+                    'key' => $extensionKey
+                ],
+                self::TRIGGER_RefreshTopbar => true
+            ]);
         }
     }
 
@@ -131,7 +138,9 @@ class ConfigurationController extends AbstractModuleController
     public function saveAndCloseAction(array $config, $extensionKey)
     {
         $this->saveConfiguration($config, $extensionKey);
-        $this->redirect('index', 'List');
+        $this->redirect('index', 'List', null, [
+            self::TRIGGER_RefreshTopbar => true
+        ]);
     }
 
     /**
diff --git a/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php b/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php
index 64fabba6ec43..cb53d8f584dc 100644
--- a/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php
+++ b/typo3/sysext/extensionmanager/Classes/Controller/UploadExtensionFileController.php
@@ -179,7 +179,10 @@ class UploadExtensionFileController extends AbstractController
             $this->removeExtensionAndRestoreFromBackup($fileName);
             $this->addFlashMessage($exception->getMessage(), '', FlashMessage::ERROR);
         }
-        $this->redirect('index', 'List', null, [self::TRIGGER_RefreshModuleMenu => true]);
+        $this->redirect('index', 'List', null, [
+            self::TRIGGER_RefreshModuleMenu => true,
+            self::TRIGGER_RefreshTopbar => true
+        ]);
     }
 
     /**
diff --git a/typo3/sysext/extensionmanager/Classes/ViewHelpers/Be/TriggerViewHelper.php b/typo3/sysext/extensionmanager/Classes/ViewHelpers/Be/TriggerViewHelper.php
index e09512bef413..11a2af7974ba 100644
--- a/typo3/sysext/extensionmanager/Classes/ViewHelpers/Be/TriggerViewHelper.php
+++ b/typo3/sysext/extensionmanager/Classes/ViewHelpers/Be/TriggerViewHelper.php
@@ -60,6 +60,13 @@ class TriggerViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Be\AbstractBackendV
                 'if (top && top.TYPO3.ModuleMenu.App) { top.TYPO3.ModuleMenu.App.refreshMenu(); }'
             );
         }
+
+        if (!empty($this->arguments['triggers'][AbstractController::TRIGGER_RefreshTopbar])) {
+            $pageRenderer->addJsInlineCode(
+                AbstractController::TRIGGER_RefreshTopbar,
+                'if (top && top.TYPO3.Backend && top.TYPO3.Backend.Topbar) { top.TYPO3.Backend.Topbar.refresh(); }'
+            );
+        }
         return '';
     }
 }
diff --git a/typo3/sysext/opendocs/Resources/Public/JavaScript/Toolbar/OpendocsMenu.js b/typo3/sysext/opendocs/Resources/Public/JavaScript/Toolbar/OpendocsMenu.js
index da228a78fd42..a690a23bf46d 100644
--- a/typo3/sysext/opendocs/Resources/Public/JavaScript/Toolbar/OpendocsMenu.js
+++ b/typo3/sysext/opendocs/Resources/Public/JavaScript/Toolbar/OpendocsMenu.js
@@ -17,7 +17,11 @@
  *  - navigating to the documents
  *  - updating the menu
  */
-define(['jquery', 'TYPO3/CMS/Backend/Icons'], function($, Icons) {
+define([
+	'jquery',
+	'TYPO3/CMS/Backend/Icons',
+	'TYPO3/CMS/Backend/Viewport'
+], function($, Icons, Viewport) {
 	'use strict';
 
 	/**
@@ -113,7 +117,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons'], function($, Icons) {
 		$(OpendocsMenu.options.containerSelector).toggleClass('open');
 	};
 
-	$(function() {
+	Viewport.Topbar.Toolbar.registerEvent(function() {
 		OpendocsMenu.initializeEvents();
 		OpendocsMenu.updateMenu();
 	});
diff --git a/typo3/sysext/setup/Classes/Controller/SetupModuleController.php b/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
index 71d4d6c42328..fbf0d7c64718 100644
--- a/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
+++ b/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
@@ -299,6 +299,8 @@ class SetupModuleController extends AbstractModule
                 }
                 // Restore admin status after processing
                 $beUser->user['admin'] = $isAdmin;
+
+                BackendUtility::setUpdateSignal('updateTopbar');
             }
         }
     }
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Toolbar/WorkspacesMenu.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Toolbar/WorkspacesMenu.js
index 2a57f0dc2440..c73d780a5946 100644
--- a/typo3/sysext/workspaces/Resources/Public/JavaScript/Toolbar/WorkspacesMenu.js
+++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/Toolbar/WorkspacesMenu.js
@@ -16,7 +16,7 @@
  * toolbar menu for the workspaces functionality to switch between the workspaces
  * and jump to the workspaces module
  */
-define(['jquery'], function($) {
+define(['jquery', 'TYPO3/CMS/Backend/Viewport'], function($, Viewport) {
 	'use strict';
 
 	/**
@@ -160,7 +160,7 @@ define(['jquery'], function($) {
 		}
 	};
 
-	$(function() {
+	Viewport.Topbar.Toolbar.registerEvent(function() {
 		WorkspacesMenu.initializeEvents();
 		WorkspacesMenu.updateBackendContext();
 	});
-- 
GitLab