diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts
index 86d12100ea8260d0646769f32bec681c913d056c..aae1d5e39f933209cc2f1d1a250b63340ee21ead 100644
--- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts
@@ -11,6 +11,8 @@
  * The TYPO3 project - inspiring people to share!
  */
 
+import documentService = require('TYPO3/CMS/Core/DocumentService');
+
 type HTMLFormChildElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
 
 /**
@@ -37,25 +39,9 @@ class GlobalEventHandler {
   };
 
   constructor() {
-    this.onReady(() => {
-      this.registerEvents();
-    });
+    documentService.ready().then((): void => this.registerEvents());
   };
 
-  private onReady(callback: Function): void {
-    if (document.readyState === 'complete') {
-      callback.call(this);
-    } else {
-      const delegate = () => {
-        window.removeEventListener('load', delegate);
-        document.removeEventListener('DOMContentLoaded', delegate);
-        callback.call(this);
-      };
-      window.addEventListener('load', delegate);
-      document.addEventListener('DOMContentLoaded', delegate);
-    }
-  }
-
   private registerEvents(): void {
     document.querySelectorAll(this.options.onChangeSelector).forEach((element: HTMLElement) => {
       document.addEventListener('change', this.handleChangeEvent.bind(this));
diff --git a/Build/Sources/TypeScript/core/Resources/Public/TypeScript/DocumentService.ts b/Build/Sources/TypeScript/core/Resources/Public/TypeScript/DocumentService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..694c794fac82c7f5a4152d8342caaa193e31910c
--- /dev/null
+++ b/Build/Sources/TypeScript/core/Resources/Public/TypeScript/DocumentService.ts
@@ -0,0 +1,59 @@
+/*
+ * 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!
+ */
+
+/**
+ * Module: TYPO3/CMS/Core/DocumentService
+ * @exports TYPO3/CMS/Core/DocumentService
+ */
+class DocumentService {
+  private readonly windowRef: Window;
+  private readonly documentRef: Document;
+
+  /**
+   * @param {Window} windowRef
+   * @param {Document} documentRef
+   */
+  constructor(windowRef: Window = window, documentRef: Document = document) {
+    this.windowRef = windowRef;
+    this.documentRef = documentRef;
+  }
+
+  ready(): Promise<Document> {
+    return new Promise<Document>((resolve: Function, reject: Function) => {
+      if (this.documentRef.readyState === 'complete') {
+        resolve(this.documentRef);
+      } else {
+        // timeout & reject after 30 seconds
+        const timer = setTimeout((): void => {
+          clearListeners();
+          reject(this.documentRef);
+        }, 30000);
+        const clearListeners = (): void => {
+          this.windowRef.removeEventListener('load', delegate);
+          this.documentRef.removeEventListener('DOMContentLoaded', delegate);
+        };
+        const delegate = (): void => {
+          clearListeners();
+          clearTimeout(timer);
+          resolve(this.documentRef);
+        };
+        this.windowRef.addEventListener('load', delegate);
+        this.documentRef.addEventListener('DOMContentLoaded', delegate);
+      }
+
+    });
+  }
+}
+
+const documentService = new DocumentService();
+export = documentService;
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/GlobalEventHandler.js b/typo3/sysext/backend/Resources/Public/JavaScript/GlobalEventHandler.js
index 2bf7e9f1e76af98de3426633ddde6109b37fcf32..8a14ce2355d84610b98bee5657faaa01549f8c24 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/GlobalEventHandler.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/GlobalEventHandler.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports"],(function(e,t){"use strict";return new class{constructor(){this.options={onChangeSelector:'[data-global-event="change"]',onClickSelector:'[data-global-event="click"]'},this.onReady(()=>{this.registerEvents()})}onReady(e){if("complete"===document.readyState)e.call(this);else{const t=()=>{window.removeEventListener("load",t),document.removeEventListener("DOMContentLoaded",t),e.call(this)};window.addEventListener("load",t),document.addEventListener("DOMContentLoaded",t)}}registerEvents(){document.querySelectorAll(this.options.onChangeSelector).forEach(e=>{document.addEventListener("change",this.handleChangeEvent.bind(this))}),document.querySelectorAll(this.options.onClickSelector).forEach(e=>{document.addEventListener("click",this.handleClickEvent.bind(this))})}handleChangeEvent(e){const t=e.target;this.handleSubmitAction(e,t)||this.handleNavigateAction(e,t)}handleClickEvent(e){e.currentTarget}handleSubmitAction(e,t){const n=t.dataset.actionSubmit;if(!n)return!1;if("$form"===n&&this.isHTMLFormChildElement(t))return t.form.submit(),!0;const o=document.querySelector(n);return o instanceof HTMLFormElement&&(o.submit(),!0)}handleNavigateAction(e,t){const n=t.dataset.actionNavigate;if(!n)return!1;const o=this.resolveHTMLFormChildElementValue(t);return!("$value"!==n||!o)&&(window.location.href=o,!0)}isHTMLFormChildElement(e){return e instanceof HTMLSelectElement||e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}resolveHTMLFormChildElementValue(e){return e instanceof HTMLSelectElement?e.options[e.selectedIndex].value:e instanceof HTMLInputElement?e.value:null}}}));
\ No newline at end of file
+define(["require","exports","TYPO3/CMS/Core/DocumentService"],(function(e,t,n){"use strict";return new class{constructor(){this.options={onChangeSelector:'[data-global-event="change"]',onClickSelector:'[data-global-event="click"]'},n.ready().then(()=>this.registerEvents())}registerEvents(){document.querySelectorAll(this.options.onChangeSelector).forEach(e=>{document.addEventListener("change",this.handleChangeEvent.bind(this))}),document.querySelectorAll(this.options.onClickSelector).forEach(e=>{document.addEventListener("click",this.handleClickEvent.bind(this))})}handleChangeEvent(e){const t=e.target;this.handleSubmitAction(e,t)||this.handleNavigateAction(e,t)}handleClickEvent(e){e.currentTarget}handleSubmitAction(e,t){const n=t.dataset.actionSubmit;if(!n)return!1;if("$form"===n&&this.isHTMLFormChildElement(t))return t.form.submit(),!0;const i=document.querySelector(n);return i instanceof HTMLFormElement&&(i.submit(),!0)}handleNavigateAction(e,t){const n=t.dataset.actionNavigate;if(!n)return!1;const i=this.resolveHTMLFormChildElementValue(t);return!("$value"!==n||!i)&&(window.location.href=i,!0)}isHTMLFormChildElement(e){return e instanceof HTMLSelectElement||e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}resolveHTMLFormChildElementValue(e){return e instanceof HTMLSelectElement?e.options[e.selectedIndex].value:e instanceof HTMLInputElement?e.value:null}}}));
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/10.4/Feature-91122-IntroduceDocumentServiceAsJQueryreadySubstitute.rst b/typo3/sysext/core/Documentation/Changelog/10.4/Feature-91122-IntroduceDocumentServiceAsJQueryreadySubstitute.rst
new file mode 100644
index 0000000000000000000000000000000000000000..cb22c298dfd8013ad1de496df8fb40c7bb0de2a1
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/10.4/Feature-91122-IntroduceDocumentServiceAsJQueryreadySubstitute.rst
@@ -0,0 +1,40 @@
+.. include:: ../../Includes.txt
+
+======================================================================
+Feature: #91122 - Introduce DocumentService as JQuery.ready substitute
+======================================================================
+
+See :issue:`91122`
+
+Description
+===========
+
+The module :js:`TYPO3/CMS/Core/DocumentService` provides native JavaScript
+functions to detect DOM ready-state returning a :js:`Promise<Document>`.
+
+Internally the Promise is resolved when native :js:`DOMContentLoaded` event has
+been emitted or when :js:`document.readyState` is defined already. It means
+that initial HTML document has been completely loaded and parsed, without
+waiting for stylesheets, images, and subframes to finish loading.
+
+
+Impact
+======
+
+.. code-block:: javascript
+
+   $(document).ready(() => {
+     // your application code
+   });
+
+Above JQuery code can be transformed into the following using :js:`DocumentService`:
+
+.. code-block:: javascript
+
+   require(['TYPO3/CMS/Core/DocumentService'], function (DocumentService) {
+     DocumentService.ready().then(() => {
+       // your application code
+     });
+   });
+
+.. index:: Backend, JavaScript, ext:core
diff --git a/typo3/sysext/core/Resources/Public/JavaScript/DocumentService.js b/typo3/sysext/core/Resources/Public/JavaScript/DocumentService.js
new file mode 100644
index 0000000000000000000000000000000000000000..1a5be706fbe1737b446c6e6729ee48e05f8a25a1
--- /dev/null
+++ b/typo3/sysext/core/Resources/Public/JavaScript/DocumentService.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"],(function(e,t){"use strict";return new class{constructor(e=window,t=document){this.windowRef=e,this.documentRef=t}ready(){return new Promise((e,t)=>{if("complete"===this.documentRef.readyState)e(this.documentRef);else{const n=setTimeout(()=>{o(),t(this.documentRef)},3e4),o=()=>{this.windowRef.removeEventListener("load",i),this.documentRef.removeEventListener("DOMContentLoaded",i)},i=()=>{o(),clearTimeout(n),e(this.documentRef)};this.windowRef.addEventListener("load",i),this.documentRef.addEventListener("DOMContentLoaded",i)}})}}}));
\ No newline at end of file