diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngine/InlineRelation/AjaxDispatcher.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngine/InlineRelation/AjaxDispatcher.ts
index 143126e02c643bc19377b5caf9e79ac493282cc2..3293d5d3d0aaa493e12cf77901251415b81704f0 100644
--- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngine/InlineRelation/AjaxDispatcher.ts
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/FormEngine/InlineRelation/AjaxDispatcher.ts
@@ -13,6 +13,7 @@
 
 import {AjaxResponse} from 'TYPO3/CMS/Core/Ajax/AjaxResponse';
 import AjaxRequest = require('TYPO3/CMS/Core/Ajax/AjaxRequest');
+import javaScriptHandler = require('TYPO3/CMS/Core/JavaScriptHandler');
 import Notification = require('../../Notification');
 import Utility = require('../../Utility');
 
@@ -33,6 +34,7 @@ interface Response {
   inlineData: object;
   requireJsModules: string[];
   scriptCall: string[];
+  scriptItems?: any[];
 }
 
 export class AjaxDispatcher {
@@ -117,14 +119,18 @@ export class AjaxDispatcher {
       TYPO3.settings.FormEngineInline = Utility.mergeDeep(TYPO3.settings.FormEngineInline, json.inlineData);
     }
 
+    if (json.scriptItems instanceof Array && json.scriptItems.length > 0) {
+      javaScriptHandler.processItems(json.scriptItems, true);
+    }
+
+    // @todo deprecate or remove with TYPO3 v12.0
     if (typeof json.requireJsModules === 'object') {
       for (let requireJsModule of json.requireJsModules) {
-        // @todo https://forge.typo3.org/issues/95874
         new Function(requireJsModule)();
       }
     }
-
     // TODO: This is subject to be removed
+    // @todo deprecate or remove with TYPO3 v12.0
     if (json.scriptCall && json.scriptCall.length > 0) {
       for (const scriptCall of json.scriptCall) {
         // eslint-disable-next-line no-eval
diff --git a/Build/Sources/TypeScript/redirects/Resources/Public/TypeScript/FormEngineEvaluation.ts b/Build/Sources/TypeScript/redirects/Resources/Public/TypeScript/FormEngineEvaluation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..21a293e530be5f5d61252b605282b3ce948e3111
--- /dev/null
+++ b/Build/Sources/TypeScript/redirects/Resources/Public/TypeScript/FormEngineEvaluation.ts
@@ -0,0 +1,34 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+import FormEngineValidation = require('TYPO3/CMS/Backend/FormEngineValidation')
+
+/**
+ * Module: TYPO3/CMS/Redirects/FormEngineEvaluation
+ * @exports TYPO3/CMS/Redirects/FormEngineEvaluation
+ */
+export class FormEngineEvaluation {
+  static registerCustomEvaluation(name: string): void {
+    FormEngineValidation.registerCustomEvaluation(name, FormEngineEvaluation.evaluateSourceHost);
+  }
+
+  public static evaluateSourceHost(value: string): string {
+    if (value === '*') {
+      return value;
+    }
+    if (!value.includes('://')) {
+      value = 'http://' + value;
+    }
+    return (new URL(value)).host;
+  }
+}
diff --git a/Build/types/TYPO3/index.d.ts b/Build/types/TYPO3/index.d.ts
index c3e77596006d844f1a4da74617b78d8156dbb260..35117edf8356d20e2078fc211a4cf97596de194a 100644
--- a/Build/types/TYPO3/index.d.ts
+++ b/Build/types/TYPO3/index.d.ts
@@ -42,6 +42,7 @@ declare namespace TYPO3 {
         public readonly errorClass: string;
         public markFieldAsChanged(field: HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement|JQuery): void;
         public initializeInputFields(): void;
+        public registerCustomEvaluation(name: string, handler: Function): void;
         public validate(section?: Element): void;
         public validateField(field: HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement|JQuery, value?: string): void;
       }
diff --git a/typo3/sysext/backend/Classes/Controller/AbstractFormEngineAjaxController.php b/typo3/sysext/backend/Classes/Controller/AbstractFormEngineAjaxController.php
index 340ff8e6b4be4bbd26cdaec552b124849704ab34..0100b26aca0eb64ceb95c43c08ae57475d58fdbb 100644
--- a/typo3/sysext/backend/Classes/Controller/AbstractFormEngineAjaxController.php
+++ b/typo3/sysext/backend/Classes/Controller/AbstractFormEngineAjaxController.php
@@ -20,6 +20,7 @@ namespace TYPO3\CMS\Backend\Controller;
 use TYPO3\CMS\Backend\Form\FormResultTrait;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Localization\LocalizationFactory;
+use TYPO3\CMS\Core\Page\JavaScriptItems;
 use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -41,9 +42,10 @@ abstract class AbstractFormEngineAjaxController
      * that need to be loaded and evaluated by JavaScript.
      *
      * @param array $result
+     * @param bool $skipInstructions whether to skip `JavaScriptModuleInstruction`
      * @return array
      */
-    protected function createExecutableStringRepresentationOfRegisteredRequireJsModules(array $result): array
+    protected function createExecutableStringRepresentationOfRegisteredRequireJsModules(array $result, bool $skipInstructions = false): array
     {
         if (empty($result['requireJsModules'])) {
             return [];
@@ -54,6 +56,9 @@ abstract class AbstractFormEngineAjaxController
             $callback = null;
             // @todo This is a temporary "solution" and shall be handled in JavaScript directly
             if ($module instanceof JavaScriptModuleInstruction) {
+                if ($skipInstructions) {
+                    continue;
+                }
                 $moduleName = $module->getName();
                 $callbackRef = $module->getExportName() ? '__esModule' : 'subjectRef';
                 $inlineCode = $this->serializeJavaScriptModuleInstructionItems($module);
@@ -83,6 +88,15 @@ abstract class AbstractFormEngineAjaxController
         return $requireJs;
     }
 
+    protected function addRegisteredRequireJsModulesToJavaScriptItems(array $result, JavaScriptItems $items): void
+    {
+        foreach ($result['requireJsModules'] as $module) {
+            if ($module instanceof JavaScriptModuleInstruction) {
+                $items->addJavaScriptModuleInstruction($module);
+            }
+        }
+    }
+
     /**
      * Resolve a CSS file position, possibly prefixed with 'EXT:'
      *
diff --git a/typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php b/typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php
index a9d4c86b403569017d0409fcc14d43bddb51c9c2..a94919a0513344aeb9a2e441c5fd50fd3b4b5382 100644
--- a/typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php
+++ b/typo3/sysext/backend/Classes/Controller/FormFlexAjaxController.php
@@ -25,6 +25,7 @@ use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
 use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
 use TYPO3\CMS\Core\Http\JsonResponse;
+use TYPO3\CMS\Core\Page\JavaScriptItems;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
@@ -157,13 +158,16 @@ class FormFlexAjaxController extends AbstractFormEngineAjaxController
         $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
         $formData['renderType'] = 'flexFormContainerContainer';
         $newContainerResult = $nodeFactory->create($formData)->render();
+        $scriptItems = GeneralUtility::makeInstance(JavaScriptItems::class);
 
         $jsonResult = [
             'html' => $newContainerResult['html'],
             'stylesheetFiles' => [],
+            'scriptItems' => $scriptItems,
             'scriptCall' => [],
         ];
 
+        // @todo deprecate with TYPO3 v12.0
         foreach ($newContainerResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
             $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
         }
@@ -178,22 +182,11 @@ class FormFlexAjaxController extends AbstractFormEngineAjaxController
                     $this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
                 );
             }
-            $javaScriptCode = [];
-            $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
-            $javaScriptCode[] = '   TYPO3.lang = {}';
-            $javaScriptCode[] = '}';
-            $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
-            $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
-            $javaScriptCode[] = '   if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
-            $javaScriptCode[] = '       TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
-            $javaScriptCode[] = '   }';
-            $javaScriptCode[] = '}';
-
-            $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
+            $scriptItems->addGlobalAssignment(['TYPO3' => ['lang' => $labels]]);
         }
-
-        $requireJsModule = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($newContainerResult);
-        $jsonResult['scriptCall'] = array_merge($requireJsModule, $jsonResult['scriptCall']);
+        $this->addRegisteredRequireJsModulesToJavaScriptItems($newContainerResult, $scriptItems);
+        // @todo deprecate modules with arbitrary JavaScript callback function in TYPO3 v12.0
+        $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($newContainerResult);
 
         return new JsonResponse($jsonResult);
     }
diff --git a/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php b/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php
index cc60ed36f1ef1ef737dfbb525f5d9d1de3146562..bdf872b3f147fbcbc5d07e5feee1a03f939faa59 100644
--- a/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php
+++ b/typo3/sysext/backend/Classes/Controller/FormInlineAjaxController.php
@@ -28,6 +28,8 @@ use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Http\JsonResponse;
 use TYPO3\CMS\Core\Messaging\AbstractMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Page\JavaScriptItems;
+use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -144,6 +146,7 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController
         $jsonArray = [
             'data' => '',
             'stylesheetFiles' => [],
+            'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class),
             'scriptCall' => [],
             'compilerInput' => [
                 'uid' => $childData['databaseRow']['uid'],
@@ -216,6 +219,7 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController
         $jsonArray = [
             'data' => '',
             'stylesheetFiles' => [],
+            'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class),
             'scriptCall' => [],
         ];
 
@@ -248,6 +252,7 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController
         $jsonArray = [
             'data' => '',
             'stylesheetFiles' => [],
+            'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class),
             'compilerInput' => [
                 'localize' => [],
             ],
@@ -539,6 +544,9 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController
      */
     protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult)
     {
+        /** @var JavaScriptItems $scriptItems */
+        $scriptItems = $jsonResult['scriptItems'];
+
         $jsonResult['data'] .= $childResult['html'];
         $jsonResult['stylesheetFiles'] = [];
         foreach ($childResult['stylesheetFiles'] as $stylesheetFile) {
@@ -547,6 +555,7 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController
         if (!empty($childResult['inlineData'])) {
             $jsonResult['inlineData'] = $childResult['inlineData'];
         }
+        // @todo deprecate with TYPO3 v12.0
         foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
             $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
         }
@@ -558,20 +567,16 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController
                     $this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
                 );
             }
-            $javaScriptCode = [];
-            $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
-            $javaScriptCode[] = '   TYPO3.lang = {}';
-            $javaScriptCode[] = '}';
-            $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
-            $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
-            $javaScriptCode[] = '   if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
-            $javaScriptCode[] = '       TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
-            $javaScriptCode[] = '   }';
-            $javaScriptCode[] = '}';
-
-            $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
+            $scriptItems->addGlobalAssignment(['TYPO3' => ['lang' => $labels]]);
+        }
+        foreach ($childResult['requireJsModules'] ?? [] as $module) {
+            if ($module instanceof JavaScriptModuleInstruction) {
+                $scriptItems->addJavaScriptModuleInstruction($module);
+            }
         }
-        $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult);
+        $this->addRegisteredRequireJsModulesToJavaScriptItems($childResult, $scriptItems);
+        // @todo deprecate modules with arbitrary JavaScript callback function in TYPO3 v12.0
+        $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult, true);
 
         return $jsonResult;
     }
@@ -669,6 +674,8 @@ class FormInlineAjaxController extends AbstractFormEngineAjaxController
      *
      * @param string $message The error message to be shown
      * @return array The error message in a JSON array
+     * @todo remove with TYPO3 v12.0
+     * @internal
      */
     protected function getErrorMessageForAJAX($message)
     {
diff --git a/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php b/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php
index c14d5fb939e021f25d9ae087bd8244847bbb6cf2..ad76f4e68778353b798500f35d53ca626510616e 100644
--- a/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php
+++ b/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php
@@ -26,6 +26,7 @@ use TYPO3\CMS\Backend\Form\InlineStackProcessor;
 use TYPO3\CMS\Backend\Form\NodeFactory;
 use TYPO3\CMS\Core\Http\JsonResponse;
 use TYPO3\CMS\Core\Localization\Locales;
+use TYPO3\CMS\Core\Page\JavaScriptItems;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
@@ -171,6 +172,7 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController
         $jsonArray = [
             'data' => '',
             'stylesheetFiles' => [],
+            'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class),
             'scriptCall' => [],
             'compilerInput' => [
                 'uid' => $childData['databaseRow']['uid'],
@@ -243,6 +245,7 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController
         $jsonArray = [
             'data' => '',
             'stylesheetFiles' => [],
+            'scriptItems' => GeneralUtility::makeInstance(JavaScriptItems::class),
             'scriptCall' => [],
         ];
 
@@ -316,6 +319,9 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController
      */
     protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult): array
     {
+        /** @var JavaScriptItems $scriptItems */
+        $scriptItems = $jsonResult['scriptItems'];
+
         $jsonResult['data'] .= $childResult['html'];
         $jsonResult['stylesheetFiles'] = [];
         foreach ($childResult['stylesheetFiles'] as $stylesheetFile) {
@@ -324,6 +330,7 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController
         if (!empty($childResult['inlineData'])) {
             $jsonResult['inlineData'] = $childResult['inlineData'];
         }
+        // @todo deprecate with TYPO3 v12.0
         foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
             $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
         }
@@ -335,20 +342,11 @@ class SiteInlineAjaxController extends AbstractFormEngineAjaxController
                     $this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
                 );
             }
-            $javaScriptCode = [];
-            $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
-            $javaScriptCode[] = '   TYPO3.lang = {}';
-            $javaScriptCode[] = '}';
-            $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
-            $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
-            $javaScriptCode[] = '   if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
-            $javaScriptCode[] = '       TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
-            $javaScriptCode[] = '   }';
-            $javaScriptCode[] = '}';
-
-            $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
+            $scriptItems->addGlobalAssignment(['TYPO3' => ['lang' => $labels]]);
         }
-        $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult);
+        $this->addRegisteredRequireJsModulesToJavaScriptItems($childResult, $scriptItems);
+        // @todo deprecate modules with arbitrary JavaScript callback function in TYPO3 v12.0
+        $jsonResult['requireJsModules'] = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult, true);
 
         return $jsonResult;
     }
diff --git a/typo3/sysext/backend/Classes/Form/AbstractNode.php b/typo3/sysext/backend/Classes/Form/AbstractNode.php
index b5a3ebdd615493a80cf2a83c4280b83f4238f0a4..34cef0588dbe3c93c359b1ae88b4a60244e793b3 100644
--- a/typo3/sysext/backend/Classes/Form/AbstractNode.php
+++ b/typo3/sysext/backend/Classes/Form/AbstractNode.php
@@ -95,6 +95,7 @@ abstract class AbstractNode implements NodeInterface, LoggerAwareInterface
     protected function initializeResultArray(): array
     {
         return [
+            // @todo deprecate inline JavaScript in TYPO3 v12.0
             'additionalJavaScriptPost' => [],
             'additionalHiddenFields' => [],
             'additionalInlineLanguageLabelFiles' => [],
@@ -123,6 +124,7 @@ abstract class AbstractNode implements NodeInterface, LoggerAwareInterface
         if ($mergeHtml && !empty($childReturn['html'])) {
             $existing['html'] .= LF . $childReturn['html'];
         }
+        // @todo deprecate inline JavaScript in TYPO3 v12.0
         foreach ($childReturn['additionalJavaScriptPost'] ?? [] as $value) {
             $existing['additionalJavaScriptPost'][] = $value;
         }
diff --git a/typo3/sysext/backend/Classes/Form/Element/CustomEvaluationTrait.php b/typo3/sysext/backend/Classes/Form/Element/CustomEvaluationTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..eccf56ef5a848b2d0b886fcad574af7d8ebb9de9
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/Element/CustomEvaluationTrait.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Backend\Form\Element;
+
+use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Trait handling custom `eval` implementations.
+ */
+trait CustomEvaluationTrait
+{
+    protected function resolveJavaScriptEvaluation(array $resultArray, string $name, ?object $evalObject): array
+    {
+        if (!is_object($evalObject) || !method_exists($evalObject, 'returnFieldJS')) {
+            return $resultArray;
+        }
+
+        $javaScriptEvaluation = $evalObject->returnFieldJS();
+        if ($javaScriptEvaluation instanceof JavaScriptModuleInstruction) {
+            // just use the module name and export-name
+            $resultArray['requireJsModules'][] = JavaScriptModuleInstruction::forRequireJS(
+                $javaScriptEvaluation->getName(),
+                $javaScriptEvaluation->getExportName()
+            )->invoke('registerCustomEvaluation', $name);
+        } else {
+            // @todo deprecate inline JavaScript in TYPO3 v12.0
+            $resultArray['additionalJavaScriptPost'][] = sprintf(
+                'TBE_EDITOR.customEvalFunctions[%s] = function(value) { %s };',
+                GeneralUtility::quoteJSvalue($name),
+                $javaScriptEvaluation
+            );
+        }
+
+        return $resultArray;
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php b/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php
index 1e5bc4e4580dbf059c94be5232b69b51ccaf68c9..5c2eac87d6befc42309f012a667c72efe736ea78 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php
@@ -26,6 +26,8 @@ use TYPO3\CMS\Core\Utility\StringUtility;
  */
 class InputColorPickerElement extends AbstractFormElement
 {
+    use CustomEvaluationTrait;
+
     /**
      * Default field information enabled for this element.
      *
@@ -67,7 +69,6 @@ class InputColorPickerElement extends AbstractFormElement
      */
     public function render()
     {
-        $evalData = '';
         $languageService = $this->getLanguageService();
 
         $table = $this->data['tableName'];
@@ -117,10 +118,7 @@ class InputColorPickerElement extends AbstractFormElement
                         ];
                         $itemValue = $evalObj->deevaluateFieldValue($_params);
                     }
-                    if (method_exists($evalObj, 'returnFieldJS')) {
-                        // @todo: variable $evalData must be replaced with $func
-                        $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($evalData) . '] = function(value) {' . $evalObj->returnFieldJS() . '};';
-                    }
+                    $resultArray = $this->resolveJavaScriptEvaluation($resultArray, $func, $evalObj);
                 }
             }
         }
diff --git a/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php b/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php
index d801e21917fef1e83efc9e3b9cc16024e260d161..a960e39f0d3bbf8bc27fd44bd62c574bab7077f7 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php
@@ -39,6 +39,7 @@ use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
  */
 class InputLinkElement extends AbstractFormElement
 {
+    use CustomEvaluationTrait;
     use OnFieldChangeTrait;
 
     /**
@@ -144,10 +145,7 @@ class InputLinkElement extends AbstractFormElement
                         ];
                         $itemValue = $evalObj->deevaluateFieldValue($_params);
                     }
-                    if (method_exists($evalObj, 'returnFieldJS')) {
-                        $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($func) . ']'
-                            . ' = function(value) {' . $evalObj->returnFieldJS() . '};';
-                    }
+                    $resultArray = $this->resolveJavaScriptEvaluation($resultArray, $func, $evalObj);
                 }
             }
         }
diff --git a/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php b/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
index 5226c8ea603d62651474a5a3690501a1f9fdf3af..95f9eb00c695216da2901a6fa977ca4ecc533d7e 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
@@ -30,6 +30,7 @@ use TYPO3\CMS\Core\Utility\StringUtility;
  */
 class InputTextElement extends AbstractFormElement
 {
+    use CustomEvaluationTrait;
     use OnFieldChangeTrait;
 
     /**
@@ -135,10 +136,7 @@ class InputTextElement extends AbstractFormElement
                         ];
                         $itemValue = $evalObj->deevaluateFieldValue($_params);
                     }
-                    if (method_exists($evalObj, 'returnFieldJS')) {
-                        $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($func) . ']'
-                            . ' = function(value) {' . $evalObj->returnFieldJS() . '};';
-                    }
+                    $resultArray = $this->resolveJavaScriptEvaluation($resultArray, $func, $evalObj);
                 }
             }
         }
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php
index 86740a8867d44b24ae3264a8e56536ab7d60ea02..8bde10644ed3330254050fae0f954ffc46600c9a 100644
--- a/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php
@@ -97,6 +97,7 @@ class SelectMultipleSideBySideElement extends AbstractFormElement
     protected function renderFieldControl(): array
     {
         $alternativeResult =  [
+            // @todo deprecate inline JavaScript in TYPO3 v12.0
             'additionalJavaScriptPost' => [],
             'additionalHiddenFields' => [],
             'additionalInlineLanguageLabelFiles' => [],
diff --git a/typo3/sysext/backend/Classes/Form/FormResultCompiler.php b/typo3/sysext/backend/Classes/Form/FormResultCompiler.php
index 95e56e04941b727c0897f2a75131353a007db4e0..10f5caaa2d27cd49588cdfccb70b05bd15402445 100644
--- a/typo3/sysext/backend/Classes/Form/FormResultCompiler.php
+++ b/typo3/sysext/backend/Classes/Form/FormResultCompiler.php
@@ -93,6 +93,7 @@ class FormResultCompiler
     public function mergeResult(array $resultArray)
     {
         $this->doSaveFieldName = $resultArray['doSaveFieldName'] ?? '';
+        // @todo deprecate inline JavaScript in TYPO3 v12.0
         foreach ($resultArray['additionalJavaScriptPost'] as $element) {
             $this->additionalJavaScriptPost[] = $element;
         }
@@ -253,6 +254,7 @@ class FormResultCompiler
         if (!empty($this->inlineData)) {
             $pageRenderer->addInlineSettingArray('FormEngineInline', $this->inlineData);
         }
+        // @todo deprecate inline JavaScript in TYPO3 v12.0
         $out = LF . implode(LF, $this->additionalJavaScriptPost);
 
         return $html . LF . "\t" . GeneralUtility::wrapJS($out);
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/InlineRelation/AjaxDispatcher.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/InlineRelation/AjaxDispatcher.js
index a2924f46fecae1cd0621e5842ef34ac5219611a0..f69b1d5a135307a7a1de4b2b567f9d64ecdaf497 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/InlineRelation/AjaxDispatcher.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/InlineRelation/AjaxDispatcher.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports","TYPO3/CMS/Core/Ajax/AjaxRequest","../../Notification","../../Utility"],(function(require,exports,AjaxRequest,Notification,Utility){"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.AjaxDispatcher=void 0;class AjaxDispatcher{constructor(e){this.objectGroup=null,this.objectGroup=e}newRequest(e){return new AjaxRequest(e)}getEndpoint(e){if(void 0!==TYPO3.settings.ajaxUrls[e])return TYPO3.settings.ajaxUrls[e];throw'Undefined endpoint for route "'+e+'"'}send(e,t){const s=e.post(this.createRequestBody(t)).then(async e=>this.processResponse(await e.resolve()));return s.catch(e=>{Notification.error("Error "+e.message)}),s}createRequestBody(e){const t={};for(let s=0;s<e.length;s++)t["ajax["+s+"]"]=e[s];return t["ajax[context]"]=JSON.stringify(this.getContext()),t}getContext(){let e;return void 0!==TYPO3.settings.FormEngineInline.config[this.objectGroup]&&void 0!==TYPO3.settings.FormEngineInline.config[this.objectGroup].context&&(e=TYPO3.settings.FormEngineInline.config[this.objectGroup].context),e}processResponse(json){if(json.hasErrors)for(const e of json.messages)Notification.error(e.title,e.message);if(json.stylesheetFiles)for(const[e,t]of json.stylesheetFiles.entries()){if(!t)break;const s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.href=t,document.querySelector("head").appendChild(s),delete json.stylesheetFiles[e]}if("object"==typeof json.inlineData&&(TYPO3.settings.FormEngineInline=Utility.mergeDeep(TYPO3.settings.FormEngineInline,json.inlineData)),"object"==typeof json.requireJsModules)for(let e of json.requireJsModules)new Function(e)();if(json.scriptCall&&json.scriptCall.length>0)for(const scriptCall of json.scriptCall)eval(scriptCall);return json}}exports.AjaxDispatcher=AjaxDispatcher}));
\ No newline at end of file
+define(["require","exports","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Core/JavaScriptHandler","../../Notification","../../Utility"],(function(require,exports,AjaxRequest,javaScriptHandler,Notification,Utility){"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.AjaxDispatcher=void 0;class AjaxDispatcher{constructor(e){this.objectGroup=null,this.objectGroup=e}newRequest(e){return new AjaxRequest(e)}getEndpoint(e){if(void 0!==TYPO3.settings.ajaxUrls[e])return TYPO3.settings.ajaxUrls[e];throw'Undefined endpoint for route "'+e+'"'}send(e,t){const s=e.post(this.createRequestBody(t)).then(async e=>this.processResponse(await e.resolve()));return s.catch(e=>{Notification.error("Error "+e.message)}),s}createRequestBody(e){const t={};for(let s=0;s<e.length;s++)t["ajax["+s+"]"]=e[s];return t["ajax[context]"]=JSON.stringify(this.getContext()),t}getContext(){let e;return void 0!==TYPO3.settings.FormEngineInline.config[this.objectGroup]&&void 0!==TYPO3.settings.FormEngineInline.config[this.objectGroup].context&&(e=TYPO3.settings.FormEngineInline.config[this.objectGroup].context),e}processResponse(json){if(json.hasErrors)for(const e of json.messages)Notification.error(e.title,e.message);if(json.stylesheetFiles)for(const[e,t]of json.stylesheetFiles.entries()){if(!t)break;const s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.href=t,document.querySelector("head").appendChild(s),delete json.stylesheetFiles[e]}if("object"==typeof json.inlineData&&(TYPO3.settings.FormEngineInline=Utility.mergeDeep(TYPO3.settings.FormEngineInline,json.inlineData)),json.scriptItems instanceof Array&&json.scriptItems.length>0&&javaScriptHandler.processItems(json.scriptItems,!0),"object"==typeof json.requireJsModules)for(let e of json.requireJsModules)new Function(e)();if(json.scriptCall&&json.scriptCall.length>0)for(const scriptCall of json.scriptCall)eval(scriptCall);return json}}exports.AjaxDispatcher=AjaxDispatcher}));
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
index 10b842d415537b2e670b6d7f74c21828f3080ec6..d1dc1accf8f44469581d6804ca5ef2be99bb51fe 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
@@ -45,6 +45,11 @@ define([
     passwordDummy: '********'
   };
 
+  /**
+   * @type {Map<string, Function>}
+   */
+  const customEvaluations = new Map();
+
   /**
    * Initialize validation for the first time
    */
@@ -133,6 +138,16 @@ define([
     $humanReadableField.attr('data-formengine-input-initialized', 'true');
   };
 
+  /**
+   * @param {string} name
+   * @param {Function} handler
+   */
+  FormEngineValidation.registerCustomEvaluation = function(name, handler) {
+    if (!customEvaluations.has(name)) {
+      customEvaluations.set(name, handler);
+    }
+  }
+
   /**
    * Format field value
    *
@@ -507,8 +522,12 @@ define([
         // password is only a display evaluation, we ignore it
         break;
       default:
-        if (typeof TBE_EDITOR.customEvalFunctions !== 'undefined' && typeof TBE_EDITOR.customEvalFunctions[command] === 'function') {
-          returnValue = TBE_EDITOR.customEvalFunctions[command](value);
+        if (typeof TBE_EDITOR.customEvalFunctions !== 'undefined') {
+          if (customEvaluations.has(command)) {
+            returnValue = customEvaluations.get(command).call(null, value);
+          } else if (typeof TBE_EDITOR.customEvalFunctions[command] === 'function') {
+            returnValue = TBE_EDITOR.customEvalFunctions[command](value);
+          }
         }
     }
     return returnValue;
diff --git a/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php b/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php
index 52c07eef07be2dcfc7c2bc640af3c1f1c6ec40c2..250f20a6a48ba0091823716691c16b40ccf843a1 100644
--- a/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php
+++ b/typo3/sysext/redirects/Classes/Evaluation/SourceHost.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Redirects\Evaluation;
 
+use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
 use TYPO3\CMS\Core\Utility\PathUtility;
 
 /**
@@ -28,19 +29,18 @@ use TYPO3\CMS\Core\Utility\PathUtility;
 class SourceHost
 {
     /**
-     * JavaScript code for client side validation/evaluation
-     * (invoked by FormEngine when editing redirect entities)
+     * Returns JavaScript instruction for client side validation/evaluation
+     * (invoked by FormEngine when editing redirect entities).
      *
-     * @return string JavaScript code for client side validation/evaluation
+     * Returned `JavaScriptModuleInstruction` delegates handling to corresponding
+     * RequireJS module, having a method `evaluateSourceHost` that deals with that
+     * evaluation request.
+     *
+     * @return JavaScriptModuleInstruction
      */
-    public function returnFieldJS(): string
+    public function returnFieldJS(): JavaScriptModuleInstruction
     {
-        $jsCode = [];
-        $jsCode[] = 'if (value === \'*\') {return value;}';
-        $jsCode[] = 'var parser = document.createElement(\'a\');';
-        $jsCode[] = 'parser.href = value.indexOf(\'://\') != -1 ? value : \'http://\' + value;';
-        $jsCode[] = 'return parser.host;';
-        return implode(' ', $jsCode);
+        return JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/Redirects/FormEngineEvaluation', 'FormEngineEvaluation');
     }
 
     /**
diff --git a/typo3/sysext/redirects/Resources/Public/JavaScript/FormEngineEvaluation.js b/typo3/sysext/redirects/Resources/Public/JavaScript/FormEngineEvaluation.js
new file mode 100644
index 0000000000000000000000000000000000000000..a54e08b445c41ab9b8564424a8ac2edd213bd146
--- /dev/null
+++ b/typo3/sysext/redirects/Resources/Public/JavaScript/FormEngineEvaluation.js
@@ -0,0 +1,13 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+define(["require","exports","TYPO3/CMS/Backend/FormEngineValidation"],(function(e,t,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FormEngineEvaluation=void 0;class o{static registerCustomEvaluation(e){i.registerCustomEvaluation(e,o.evaluateSourceHost)}static evaluateSourceHost(e){return"*"===e?e:(e.includes("://")||(e="http://"+e),new URL(e).host)}}t.FormEngineEvaluation=o}));
\ No newline at end of file