From 976934a36c8efde01c31ed38cafec7340562e6f4 Mon Sep 17 00:00:00 2001
From: Markus Klein <markus.klein@typo3.org>
Date: Mon, 27 Mar 2017 15:51:07 +0200
Subject: [PATCH] [BUGFIX] JS: Fix FormEngine initialization

The FormEngine initialization process needs to be very careful
when the DOM is accessed.
This patch separates the routines and encapsulates those in
a DOMready handler, which are critical.

This solves a possible race condition when JS is executed faster
than DOM is built.

Releases: master, 7.6
Resolves: #80481
Resolves: #80366
Change-Id: I205aebc9f87a25f06942f923497f7f535fdb0c8f
Reviewed-on: https://review.typo3.org/52180
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Thomas Maroschik <tmaroschik@dfau.de>
Tested-by: Thomas Maroschik <tmaroschik@dfau.de>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
---
 .../Classes/Form/FormResultCompiler.php       |  7 ++--
 .../Private/TypeScript/FormEngineReview.ts    |  8 ++--
 .../Resources/Public/JavaScript/FormEngine.js | 38 +++++++++++--------
 .../Public/JavaScript/FormEngineReview.js     |  4 +-
 .../Public/JavaScript/FormEngineValidation.js |  4 --
 5 files changed, 33 insertions(+), 28 deletions(-)

diff --git a/typo3/sysext/backend/Classes/Form/FormResultCompiler.php b/typo3/sysext/backend/Classes/Form/FormResultCompiler.php
index 97d70bcadebf..1b6da7eda5c9 100644
--- a/typo3/sysext/backend/Classes/Form/FormResultCompiler.php
+++ b/typo3/sysext/backend/Classes/Form/FormResultCompiler.php
@@ -214,9 +214,10 @@ class FormResultCompiler
         $pageRenderer->addJsFile('EXT:backend/Resources/Public/JavaScript/md5.js');
         // load the main module for FormEngine with all important JS functions
         $this->requireJsModules['TYPO3/CMS/Backend/FormEngine'] = 'function(FormEngine) {
-			FormEngine.setBrowserUrl(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('wizard_element_browser')) . ');
-			FormEngine.Validation.setUsMode(' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '1' : '0') . ');
-			FormEngine.Validation.registerReady();
+			FormEngine.initialize(
+				' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('wizard_element_browser')) . ',
+				' . ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? '1' : '0') . '
+			);
 		}';
         $this->requireJsModules['TYPO3/CMS/Backend/FormEngineReview'] = null;
 
diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/FormEngineReview.ts b/typo3/sysext/backend/Resources/Private/TypeScript/FormEngineReview.ts
index 284b4a68e9ea..b20651c3f503 100644
--- a/typo3/sysext/backend/Resources/Private/TypeScript/FormEngineReview.ts
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/FormEngineReview.ts
@@ -13,10 +13,10 @@
 
 /// <amd-dependency path="bootstrap">
 
-// todo: once FormEngineValidation is a native TypeScript class, we can use require() instead
+// todo: once FormEngine is a native TypeScript class, we can use require() instead
 // and drop amd-dependency and declare
-/// <amd-dependency path="TYPO3/CMS/Backend/FormEngineValidation" name="FormEngineValidation">
-declare let FormEngineValidation: any;
+/// <amd-dependency path="TYPO3/CMS/Backend/FormEngine" name="FormEngine">
+declare let FormEngine: any;
 declare let TYPO3: any;
 
 import $ = require('jquery');
@@ -33,7 +33,7 @@ class FormEngineReview {
      * @return {$}
      */
     public static findInvalidField(): any {
-        return $(document).find('.tab-content .' + FormEngineValidation.errorClass);
+        return $(document).find('.tab-content .' + FormEngine.Validation.errorClass);
     }
 
     /**
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
index d776cacbe7c1..0923267dcc9d 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
@@ -38,7 +38,7 @@ define(['jquery',
 
 	/**
 	 *
-	 * @type {{formName: *, openedPopupWindow: window, legacyFieldChangedCb: Function, browserUrl: string}}
+	 * @type {{Validation: object, formName: *, openedPopupWindow: window, legacyFieldChangedCb: Function, browserUrl: string}}
 	 * @exports TYPO3/CMS/Backend/FormEngine
 	 */
 	var FormEngine = {
@@ -49,14 +49,6 @@ define(['jquery',
 		browserUrl: ''
 	};
 
-	/**
-	 *
-	 * @param {String} browserUrl
-	 */
-	FormEngine.setBrowserUrl = function(browserUrl) {
-		FormEngine.browserUrl = browserUrl;
-	};
-
 	// functions to connect the db/file browser with this document and the formfields on it!
 
 	/**
@@ -589,9 +581,6 @@ define(['jquery',
 	 * as it using deferrer methods only
 	 */
 	FormEngine.initializeEvents = function() {
-
-		FormEngine.initializeSelectCheckboxes();
-
 		$(document).on('click', '.t3js-btn-moveoption-top, .t3js-btn-moveoption-up, .t3js-btn-moveoption-down, .t3js-btn-moveoption-bottom, .t3js-btn-removeoption', function(evt) {
 			evt.preventDefault();
 
@@ -1089,7 +1078,7 @@ define(['jquery',
 				}
 			});
 		} else {
-			FormEngine.closeDocument()
+			FormEngine.closeDocument();
 		}
 	};
 
@@ -1125,14 +1114,33 @@ define(['jquery',
 		document.editform.submit();
 	};
 
+	/**
+	 * Main init function called from outside
+	 *
+	 * Sets some options and registers the DOMready handler to initialize further things
+	 *
+	 * @param {String} browserUrl
+	 * @param {Number} mode
+	 */
+	FormEngine.initialize = function(browserUrl, mode) {
+		FormEngine.browserUrl = browserUrl;
+		FormEngine.Validation.setUsMode(mode);
+
+		$(function() {
+			FormEngine.initializeSelectCheckboxes();
+			FormEngine.Validation.initialize();
+			FormEngine.reinitialize();
+		});
+	};
+
 	/**
 	 * initialize function, always require possible post-render hooks return the main object
 	 */
 
-	// the functions are both using delegates, thus no need to be called again
+	// the events are only bound to the document, which is already present for sure.
+	// no need to have it in DOMready handler
 	FormEngine.initializeEvents();
 	FormEngine.SelectBoxFilter.initializeEvents();
-	FormEngine.reinitialize();
 
 	// load required modules to hook in the post initialize function
 	if (undefined !== TYPO3.settings.RequireJS && undefined !== TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine']) {
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineReview.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineReview.js
index 8d7f6d36d2b5..aebaa6422419 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineReview.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineReview.js
@@ -10,7 +10,7 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require", "exports", "TYPO3/CMS/Backend/FormEngineValidation", "jquery", "bootstrap"], function (require, exports, FormEngineValidation, $) {
+define(["require", "exports", "TYPO3/CMS/Backend/FormEngine", "jquery", "bootstrap"], function (require, exports, FormEngine, $) {
     "use strict";
     /**
      * Module: TYPO3/CMS/Backend/FormEngineReview
@@ -85,7 +85,7 @@ define(["require", "exports", "TYPO3/CMS/Backend/FormEngineValidation", "jquery"
          * @return {$}
          */
         FormEngineReview.findInvalidField = function () {
-            return $(document).find('.tab-content .' + FormEngineValidation.errorClass);
+            return $(document).find('.tab-content .' + FormEngine.Validation.errorClass);
         };
         /**
          * Renders an invisible button to toggle the review panel into the least possible toolbar
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
index b2c16fa374b9..13810fde11ec 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
@@ -1036,9 +1036,5 @@ define(['jquery', 'moment'], function ($, moment) {
 		return result;
 	};
 
-	FormEngineValidation.registerReady = function() {
-		FormEngineValidation.initialize();
-	};
-
 	return FormEngineValidation;
 });
-- 
GitLab