diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/BackendException.ts b/typo3/sysext/backend/Resources/Private/TypeScript/BackendException.ts
new file mode 100644
index 0000000000000000000000000000000000000000..384e9d0b14aa26d0cd565874be63c6bf18de6f15
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/BackendException.ts
@@ -0,0 +1,22 @@
+/*
+ * 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!
+ */
+
+export class BackendException {
+  public readonly message: string;
+  public readonly code: number;
+
+  constructor(message = '', code = 0) {
+    this.message = message;
+    this.code = code;
+  }
+}
diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/ClientRequest.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/ClientRequest.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2aa583e544f4161659c5a8371fdbcbe38d5b166b
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/ClientRequest.ts
@@ -0,0 +1,25 @@
+/*
+ * 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 InteractionRequest = require('./InteractionRequest');
+
+class ClientRequest extends InteractionRequest {
+  public readonly clientEvent: any;
+
+  constructor(type: string, clientEvent: Event = null) {
+    super(type);
+    this.clientEvent = clientEvent;
+  }
+}
+
+export = ClientRequest;
diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/Consumable.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/Consumable.ts
new file mode 100644
index 0000000000000000000000000000000000000000..845a00c7759648aa05cf892ddcd40480cb334dc3
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/Consumable.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 InteractionRequest = require('./InteractionRequest');
+
+interface Consumable {
+  consume(interactionRequest: InteractionRequest): any;
+}
+
+export = Consumable;
diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/ConsumerScope.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/ConsumerScope.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3adfe53dd279826c1afbf05eab4b683269a18320
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/ConsumerScope.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 $ = require('jquery');
+import Consumable = require('./Consumable');
+import InteractionRequest = require('./InteractionRequest');
+
+class ConsumerScope {
+  private consumers: Consumable[] = [];
+
+  public getConsumers(): Consumable[] {
+    return this.consumers;
+  }
+
+  public hasConsumer(consumer: Consumable): boolean {
+    return this.consumers.indexOf(consumer) !== -1;
+  }
+
+  public attach(consumer: Consumable) {
+    if (!this.hasConsumer(consumer)) {
+      this.consumers.push(consumer);
+    }
+  }
+
+  public detach(consumer: Consumable) {
+    this.consumers = this.consumers.filter(
+      (currentConsumer: Consumable) => currentConsumer !== consumer,
+    );
+  }
+
+  public invoke(request: InteractionRequest): any {
+    const deferreds: any[] = [];
+    this.consumers.forEach(
+      (consumer: Consumable) => {
+        const deferred: any = consumer.consume.call(consumer, request);
+        if (deferred) {
+          deferreds.push(deferred);
+        }
+      },
+    );
+    return ($ as any).when.apply($, deferreds);
+  }
+}
+
+export = new ConsumerScope();
diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequest.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequest.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e4b91971cf5ea1fe7e20b7b06291fc7234161dbb
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequest.ts
@@ -0,0 +1,47 @@
+/*
+ * 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!
+ */
+
+class InteractionRequest {
+  public readonly type: string;
+  public readonly parentRequest: InteractionRequest;
+  protected processed = false;
+  protected processedData: any = null;
+
+  public get outerMostRequest(): InteractionRequest {
+    let request: InteractionRequest = this;
+    while (request.parentRequest instanceof InteractionRequest) {
+      request = request.parentRequest;
+    }
+    return request;
+  }
+
+  constructor(type: string, parentRequest: InteractionRequest = null) {
+    this.type = type;
+    this.parentRequest = parentRequest;
+  }
+
+  public isProcessed(): boolean {
+    return this.processed;
+  }
+
+  public getProcessedData(): any {
+    return this.processedData;
+  }
+
+  public setProcessedData(processedData: any = null) {
+    this.processed = true;
+    this.processedData = processedData;
+  }
+}
+
+export = InteractionRequest;
diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestAssignment.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestAssignment.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a2a1ca8ccb267b156877b716b9bd97d4b4aa7828
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestAssignment.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 InteractionRequest = require('./InteractionRequest');
+
+interface InteractionRequestAssignment {
+  request: InteractionRequest;
+  // @todo Add type for jQuery.Deferred[]
+  deferreds: any[];
+}
+
+export = InteractionRequestAssignment;
diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestMap.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestMap.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2334eb167c6f07b21f8c271dd1949362227a9e17
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestMap.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 $ = require('jquery');
+import InteractionRequest = require('./InteractionRequest');
+import InteractionRequestAssignment = require('./InteractionRequestAssignment');
+
+class InteractionRequestMap {
+  private assignments: InteractionRequestAssignment[] = [];
+
+  public attachFor(request: InteractionRequest, deferred: any) {
+    let targetAssignment = this.getFor(request);
+    if (targetAssignment === null) {
+      targetAssignment = {request, deferreds: []} as InteractionRequestAssignment;
+      this.assignments.push(targetAssignment);
+    }
+    targetAssignment.deferreds.push(deferred);
+  }
+
+  public detachFor(request: InteractionRequest) {
+    const targetAssignment = this.getFor(request);
+    this.assignments = this.assignments.filter(
+      (assignment: InteractionRequestAssignment) => assignment === targetAssignment,
+    );
+  }
+
+  public getFor(triggerEvent: InteractionRequest): InteractionRequestAssignment {
+    let targetAssignment: InteractionRequestAssignment = null;
+    this.assignments.some(
+      (assignment: InteractionRequestAssignment) => {
+        if (assignment.request === triggerEvent) {
+          targetAssignment = assignment;
+          return true;
+        }
+        return false;
+      },
+    );
+    return targetAssignment;
+  }
+
+  public resolveFor(triggerEvent: InteractionRequest) {
+    const targetAssignment = this.getFor(triggerEvent);
+    if (targetAssignment === null) {
+      return false;
+    }
+    targetAssignment.deferreds.forEach(
+      (deferred: any) => deferred.resolve(),
+    );
+    this.detachFor(triggerEvent);
+    return true;
+  }
+
+  public rejectFor(triggerEvent: InteractionRequest) {
+    const targetAssignment = this.getFor(triggerEvent);
+    if (targetAssignment === null) {
+      return false;
+    }
+    targetAssignment.deferreds.forEach(
+      (deferred: any) => deferred.reject(),
+    );
+    this.detachFor(triggerEvent);
+    return true;
+  }
+}
+
+export = new InteractionRequestMap();
diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/TriggerRequest.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/TriggerRequest.ts
new file mode 100644
index 0000000000000000000000000000000000000000..433c6fd12886e42c2d4469cc086ac6cbadb87476
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/TriggerRequest.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 InteractionRequest = require('./InteractionRequest');
+
+class TriggerRequest extends InteractionRequest {
+  constructor(type: string, parentRequest: InteractionRequest = null) {
+    super(type, parentRequest);
+  }
+
+  public concerns(ancestorRequest: InteractionRequest): boolean {
+    if (this === ancestorRequest) {
+      return true;
+    }
+    let request: InteractionRequest = this;
+    while (request.parentRequest instanceof InteractionRequest) {
+      request = request.parentRequest;
+      if (request === ancestorRequest) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public concernsTypes(types: string[]): boolean {
+    if (types.indexOf(this.type) !== -1) {
+      return true;
+    }
+    let request: InteractionRequest = this;
+    while (request.parentRequest instanceof InteractionRequest) {
+      request = request.parentRequest;
+      if (types.indexOf(request.type) !== -1) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+export = TriggerRequest;
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/BackendException.js b/typo3/sysext/backend/Resources/Public/JavaScript/BackendException.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f752b124560a4568a8bcc7346145b21f0cd96ed
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/BackendException.js
@@ -0,0 +1,26 @@
+/*
+ * 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 (require, exports) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    var BackendException = (function () {
+        function BackendException(message, code) {
+            if (message === void 0) { message = ''; }
+            if (code === void 0) { code = 0; }
+            this.message = message;
+            this.code = code;
+        }
+        return BackendException;
+    }());
+    exports.BackendException = BackendException;
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js b/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js
index 94201460216bcb358cdf64c6e064895d81750660..999d9653813c62eebbc37486fe1e79f4a3490a2a 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js
@@ -145,7 +145,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
         var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
         url += '&CB[el][' + table + '%7C' + uid + ']=1'+ '&CB[setCopyMode]=1';
         $.ajax(url).always(function () {
-            top.list_frame.location.reload(true);
+            top.TYPO3.Backend.ContentContainer.refresh(true);
         });
     };
 
@@ -153,7 +153,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
         var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
         url += '&CB[el][' + table + '%7C' + uid + ']=0';
         $.ajax(url).always(function () {
-            top.list_frame.location.reload(true);
+            top.TYPO3.Backend.ContentContainer.refresh(true);
         });
     };
 
@@ -161,7 +161,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
         var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
         url += '&CB[el][' + table + '%7C' + uid + ']=1'+ '&CB[setCopyMode]=0';
         $.ajax(url).always(function () {
-            top.list_frame.location.reload(true);
+            top.TYPO3.Backend.ContentContainer.refresh(true);
         });
     };
 
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/ClientRequest.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/ClientRequest.js
new file mode 100644
index 0000000000000000000000000000000000000000..1052a892163512e61bcc2947845daa35170f5bb9
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/ClientRequest.js
@@ -0,0 +1,36 @@
+/*
+ * 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!
+ */
+var __extends = (this && this.__extends) || (function () {
+    var extendStatics = Object.setPrototypeOf ||
+        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+    return function (d, b) {
+        extendStatics(d, b);
+        function __() { this.constructor = d; }
+        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+    };
+})();
+define(["require", "exports", "./InteractionRequest"], function (require, exports, InteractionRequest) {
+    "use strict";
+    var ClientRequest = (function (_super) {
+        __extends(ClientRequest, _super);
+        function ClientRequest(type, clientEvent) {
+            if (clientEvent === void 0) { clientEvent = null; }
+            var _this = _super.call(this, type) || this;
+            _this.clientEvent = clientEvent;
+            return _this;
+        }
+        return ClientRequest;
+    }(InteractionRequest));
+    return ClientRequest;
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/Consumable.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/Consumable.js
new file mode 100644
index 0000000000000000000000000000000000000000..fcabab445aeb0e5dd421a2916443631a558bd7b4
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/Consumable.js
@@ -0,0 +1,16 @@
+/*
+ * 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 (require, exports) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/ConsumerScope.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/ConsumerScope.js
new file mode 100644
index 0000000000000000000000000000000000000000..440cab88b6b00aca273387b905b9503c6176960f
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/ConsumerScope.js
@@ -0,0 +1,46 @@
+/*
+ * 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", "jquery"], function (require, exports, $) {
+    "use strict";
+    var ConsumerScope = (function () {
+        function ConsumerScope() {
+            this.consumers = [];
+        }
+        ConsumerScope.prototype.getConsumers = function () {
+            return this.consumers;
+        };
+        ConsumerScope.prototype.hasConsumer = function (consumer) {
+            return this.consumers.indexOf(consumer) !== -1;
+        };
+        ConsumerScope.prototype.attach = function (consumer) {
+            if (!this.hasConsumer(consumer)) {
+                this.consumers.push(consumer);
+            }
+        };
+        ConsumerScope.prototype.detach = function (consumer) {
+            this.consumers = this.consumers.filter(function (currentConsumer) { return currentConsumer !== consumer; });
+        };
+        ConsumerScope.prototype.invoke = function (request) {
+            var deferreds = [];
+            this.consumers.forEach(function (consumer) {
+                var deferred = consumer.consume.call(consumer, request);
+                if (deferred) {
+                    deferreds.push(deferred);
+                }
+            });
+            return $.when.apply($, deferreds);
+        };
+        return ConsumerScope;
+    }());
+    return new ConsumerScope();
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequest.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequest.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b726f5353ea7d2f07fe950476f3107c2a063dde
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequest.js
@@ -0,0 +1,48 @@
+/*
+ * 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 (require, exports) {
+    "use strict";
+    var InteractionRequest = (function () {
+        function InteractionRequest(type, parentRequest) {
+            if (parentRequest === void 0) { parentRequest = null; }
+            this.processed = false;
+            this.processedData = null;
+            this.type = type;
+            this.parentRequest = parentRequest;
+        }
+        Object.defineProperty(InteractionRequest.prototype, "outerMostRequest", {
+            get: function () {
+                var request = this;
+                while (request.parentRequest instanceof InteractionRequest) {
+                    request = request.parentRequest;
+                }
+                return request;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        InteractionRequest.prototype.isProcessed = function () {
+            return this.processed;
+        };
+        InteractionRequest.prototype.getProcessedData = function () {
+            return this.processedData;
+        };
+        InteractionRequest.prototype.setProcessedData = function (processedData) {
+            if (processedData === void 0) { processedData = null; }
+            this.processed = true;
+            this.processedData = processedData;
+        };
+        return InteractionRequest;
+    }());
+    return InteractionRequest;
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestAssignment.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestAssignment.js
new file mode 100644
index 0000000000000000000000000000000000000000..fcabab445aeb0e5dd421a2916443631a558bd7b4
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestAssignment.js
@@ -0,0 +1,16 @@
+/*
+ * 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 (require, exports) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestMap.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestMap.js
new file mode 100644
index 0000000000000000000000000000000000000000..9983ef9423a64aeca35915b25195bfdc6506f7f3
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestMap.js
@@ -0,0 +1,63 @@
+/*
+ * 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 (require, exports) {
+    "use strict";
+    var InteractionRequestMap = (function () {
+        function InteractionRequestMap() {
+            this.assignments = [];
+        }
+        InteractionRequestMap.prototype.attachFor = function (request, deferred) {
+            var targetAssignment = this.getFor(request);
+            if (targetAssignment === null) {
+                targetAssignment = { request: request, deferreds: [] };
+                this.assignments.push(targetAssignment);
+            }
+            targetAssignment.deferreds.push(deferred);
+        };
+        InteractionRequestMap.prototype.detachFor = function (request) {
+            var targetAssignment = this.getFor(request);
+            this.assignments = this.assignments.filter(function (assignment) { return assignment === targetAssignment; });
+        };
+        InteractionRequestMap.prototype.getFor = function (triggerEvent) {
+            var targetAssignment = null;
+            this.assignments.some(function (assignment) {
+                if (assignment.request === triggerEvent) {
+                    targetAssignment = assignment;
+                    return true;
+                }
+                return false;
+            });
+            return targetAssignment;
+        };
+        InteractionRequestMap.prototype.resolveFor = function (triggerEvent) {
+            var targetAssignment = this.getFor(triggerEvent);
+            if (targetAssignment === null) {
+                return false;
+            }
+            targetAssignment.deferreds.forEach(function (deferred) { return deferred.resolve(); });
+            this.detachFor(triggerEvent);
+            return true;
+        };
+        InteractionRequestMap.prototype.rejectFor = function (triggerEvent) {
+            var targetAssignment = this.getFor(triggerEvent);
+            if (targetAssignment === null) {
+                return false;
+            }
+            targetAssignment.deferreds.forEach(function (deferred) { return deferred.reject(); });
+            this.detachFor(triggerEvent);
+            return true;
+        };
+        return InteractionRequestMap;
+    }());
+    return new InteractionRequestMap();
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/TriggerRequest.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/TriggerRequest.js
new file mode 100644
index 0000000000000000000000000000000000000000..28d91dfb16c680d52b8403ec555779beb047af33
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/TriggerRequest.js
@@ -0,0 +1,60 @@
+/*
+ * 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!
+ */
+var __extends = (this && this.__extends) || (function () {
+    var extendStatics = Object.setPrototypeOf ||
+        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+    return function (d, b) {
+        extendStatics(d, b);
+        function __() { this.constructor = d; }
+        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+    };
+})();
+define(["require", "exports", "./InteractionRequest"], function (require, exports, InteractionRequest) {
+    "use strict";
+    var TriggerRequest = (function (_super) {
+        __extends(TriggerRequest, _super);
+        function TriggerRequest(type, parentRequest) {
+            if (parentRequest === void 0) { parentRequest = null; }
+            return _super.call(this, type, parentRequest) || this;
+        }
+        TriggerRequest.prototype.concerns = function (ancestorRequest) {
+            if (this === ancestorRequest) {
+                return true;
+            }
+            var request = this;
+            while (request.parentRequest instanceof InteractionRequest) {
+                request = request.parentRequest;
+                if (request === ancestorRequest) {
+                    return true;
+                }
+            }
+            return false;
+        };
+        TriggerRequest.prototype.concernsTypes = function (types) {
+            if (types.indexOf(this.type) !== -1) {
+                return true;
+            }
+            var request = this;
+            while (request.parentRequest instanceof InteractionRequest) {
+                request = request.parentRequest;
+                if (types.indexOf(request.type) !== -1) {
+                    return true;
+                }
+            }
+            return false;
+        };
+        return TriggerRequest;
+    }(InteractionRequest));
+    return TriggerRequest;
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
index edf73ce6aa0cb2d783e3ea97344ef4993534d2c8..a88a42f6b6c569300c70162cfad1265bf89fd9c3 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
@@ -33,16 +33,30 @@ var setFormValueOpenBrowser,
 define(['jquery',
 		'TYPO3/CMS/Backend/FormEngineValidation',
 		'TYPO3/CMS/Backend/Modal',
-		'TYPO3/CMS/Backend/Severity'
-	   ], function ($, FormEngineValidation, Modal, Severity) {
+		'TYPO3/CMS/Backend/Severity',
+		'TYPO3/CMS/Backend/BackendException',
+		'TYPO3/CMS/Backend/Event/InteractionRequestMap'
+	   ], function ($, FormEngineValidation, Modal, Severity, BackendException, InteractionRequestMap) {
+
+	/**
+	 * @param {InteractionRequest} interactionRequest
+	 * @param {boolean} response
+	 */
+	function handleConsumeResponse(interactionRequest, response) {
+		if (response) {
+			FormEngine.interactionRequestMap.resolveFor(interactionRequest);
+		} else {
+			FormEngine.interactionRequestMap.rejectFor(interactionRequest);
+		}
+	}
 
 	/**
-	 *
-	 * @type {{Validation: object, formName: *, openedPopupWindow: window, legacyFieldChangedCb: Function, browserUrl: string}}
 	 * @exports TYPO3/CMS/Backend/FormEngine
 	 */
 	var FormEngine = {
+		consumeTypes: ['typo3.setUrl', 'typo3.beforeSetUrl', 'typo3.refresh'],
 		Validation: FormEngineValidation,
+		interactionRequestMap: InteractionRequestMap,
 		formName: TYPO3.settings.FormEngine.formName,
 		openedPopupWindow: null,
 		legacyFieldChangedCb: function() { !$.isFunction(TYPO3.settings.FormEngine.legacyFieldChangedCb) || TYPO3.settings.FormEngine.legacyFieldChangedCb(); },
@@ -67,7 +81,6 @@ define(['jquery',
 		FormEngine.openedPopupWindow.focus();
 	};
 
-
 	/**
 	 * properly fills the select field from the popup window (element browser, link browser)
 	 * or from a multi-select (two selects side-by-side)
@@ -581,6 +594,12 @@ define(['jquery',
 	 * as it using deferrer methods only
 	 */
 	FormEngine.initializeEvents = function() {
+		if (top.TYPO3 && typeof top.TYPO3.Backend !== 'undefined') {
+			top.TYPO3.Backend.consumerScope.attach(FormEngine);
+			$(window).on('unload', function() {
+				top.TYPO3.Backend.consumerScope.detach(FormEngine);
+			});
+		}
 		$(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();
 
@@ -626,7 +645,9 @@ define(['jquery',
 			}
 		}).on('click', '.t3js-editform-close', function(e) {
 			e.preventDefault();
-			FormEngine.preventExitIfNotSaved();
+			FormEngine.preventExitIfNotSaved(
+				FormEngine.preventExitIfNotSavedCallback
+			);
 		}).on('click', '.t3js-editform-delete-record', function(e) {
 			e.preventDefault();
 			var title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?';
@@ -727,6 +748,45 @@ define(['jquery',
 		});
 	};
 
+	/**
+	 * @param {InteractionRequest} interactionRequest
+	 * @return {jQuery.Deferred}
+	 */
+	FormEngine.consume = function(interactionRequest) {
+		if (!interactionRequest) {
+			throw new BackendException('No interaction request given', 1496589980);
+		}
+		if (interactionRequest.concernsTypes(FormEngine.consumeTypes)) {
+			var outerMostRequest = interactionRequest.outerMostRequest;
+			var deferred = $.Deferred();
+
+			FormEngine.interactionRequestMap.attachFor(
+				outerMostRequest,
+				deferred
+			);
+			// resolve or reject deferreds with previous user choice
+			if (outerMostRequest.isProcessed()) {
+				handleConsumeResponse(
+					outerMostRequest,
+					outerMostRequest.getProcessedData().response
+				);
+			// show confirmation dialog
+			} else if (FormEngine.hasChange()) {
+				FormEngine.preventExitIfNotSaved(function(response) {
+					outerMostRequest.setProcessedData(
+						{response: response}
+					);
+					handleConsumeResponse(outerMostRequest, response);
+				});
+			// resolve directly
+			} else {
+				FormEngine.interactionRequestMap.resolveFor(outerMostRequest);
+			}
+
+			return deferred;
+		}
+	};
+
 	/**
 	 * Initializes the remaining character views based on the fields' maxlength attribute
 	 */
@@ -1052,10 +1112,30 @@ define(['jquery',
 	};
 
 	/**
-	 * Show modal to confirm closing the document without saving
+	 * @return {boolean}
 	 */
-	FormEngine.preventExitIfNotSaved = function() {
-		if ($('form[name="' + FormEngine.formName + '"] .has-change').length > 0) {
+	FormEngine.hasChange = function() {
+		return $('form[name="' + FormEngine.formName + '"] .has-change').length > 0;
+	};
+
+	/**
+	 * @param {boolean} response
+	 */
+	FormEngine.preventExitIfNotSavedCallback = function(response) {
+		if (response) {
+			FormEngine.closeDocument();
+		}
+	};
+
+	/**
+	 * Show modal to confirm closing the document without saving.
+	 *
+	 * @param {Function} callback
+	 */
+	FormEngine.preventExitIfNotSaved = function(callback) {
+		callback = callback || FormEngine.preventExitIfNotSavedCallback;
+
+		if (FormEngine.hasChange()) {
 			var title = TYPO3.lang['label.confirm.close_without_save.title'] || 'Do you want to quit without saving?';
 			var content = TYPO3.lang['label.confirm.close_without_save.content'] || 'You have currently unsaved changes. Are you sure that you want to discard all changes?';
 			var $modal = Modal.confirm(title, content, Severity.warning, [
@@ -1074,13 +1154,14 @@ define(['jquery',
 			$modal.on('button.clicked', function(e) {
 				if (e.target.name === 'no') {
 					Modal.dismiss();
+					callback.call(null, false);
 				} else if (e.target.name === 'yes') {
 					Modal.dismiss();
-					FormEngine.closeDocument();
+					callback.call(null, true);
 				}
 			});
 		} else {
-			FormEngine.closeDocument();
+			callback.call(null, true);
 		}
 	};
 
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js
index 5b226158bfd05045e2fe1fdd81f8d59862a8c520..480585f44aa2cf3e42d6ec7978127984de893dca 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js
@@ -20,12 +20,15 @@ require(
 		'jquery',
 		'TYPO3/CMS/Backend/Storage',
 		'TYPO3/CMS/Backend/Icons',
-		'TYPO3/CMS/Backend/Viewport'
+		'TYPO3/CMS/Backend/Viewport',
+		'TYPO3/CMS/Backend/Event/ClientRequest',
+		'TYPO3/CMS/Backend/Event/TriggerRequest'
 	],
-	function ($, Storage, Icons) {
+	function ($, Storage, Icons, Viewport, ClientRequest, TriggerRequest) {
 		if (typeof TYPO3.ModuleMenu !== 'undefined') {
 			return TYPO3.ModuleMenu.App;
 		}
+
 		TYPO3.ModuleMenu = {};
 		TYPO3.ModuleMenu.App = {
 			loadedModule: null,
@@ -35,42 +38,52 @@ require(
 			initialize: function () {
 				var me = this;
 
+				var deferred = $.Deferred();
+				deferred.resolve();
+
 				// load the start module
 				if (top.startInModule && top.startInModule[0] && $('#' + top.startInModule[0]).length > 0) {
-					me.showModule(top.startInModule[0], top.startInModule[1]);
+					deferred = me.showModule(
+						top.startInModule[0],
+						top.startInModule[1]
+					);
 				} else {
 					// fetch first module
 					if ($('.t3js-mainmodule:first').attr('id')) {
-						me.showModule($('.t3js-mainmodule:first').attr('id'));
+						deferred = me.showModule(
+							$('.t3js-mainmodule:first').attr('id')
+						);
 					}
 					// else case: the main module has no entries, this is probably a backend
 					// user with very little access rights, maybe only the logout button and
 					// a user settings module in topbar.
 				}
 
-				// check if module menu should be collapsed or not
-				var state = Storage.Persistent.get('BackendComponents.States.typo3-module-menu');
-				if (state && state.collapsed) {
-					TYPO3.ModuleMenu.App.toggleMenu(state.collapsed === 'true');
-				}
-
-				// check if there are collapsed items in the users' configuration
-				var collapsedMainMenuItems = me.getCollapsedMainMenuItems();
-				$.each(collapsedMainMenuItems, function (key, itm) {
-					if (itm !== true) {
-						return;
-					}
-					var $group = $('#' + key);
-					if ($group.length > 0) {
-						var $groupContainer = $group.find('.modulemenu-group-container');
-						$group.addClass('collapsed').removeClass('expanded');
-						TYPO3.Backend.NavigationContainer.cleanup();
-						$groupContainer.hide().promise().done(function () {
-							TYPO3.Backend.doLayout();
-						});
+				deferred.then(function() {
+					// check if module menu should be collapsed or not
+					var state = Storage.Persistent.get('BackendComponents.States.typo3-module-menu');
+					if (state && state.collapsed) {
+						TYPO3.ModuleMenu.App.toggleMenu(state.collapsed === 'true');
 					}
+
+					// check if there are collapsed items in the users' configuration
+					var collapsedMainMenuItems = me.getCollapsedMainMenuItems();
+					$.each(collapsedMainMenuItems, function (key, itm) {
+						if (itm !== true) {
+							return;
+						}
+						var $group = $('#' + key);
+						if ($group.length > 0) {
+							var $groupContainer = $group.find('.modulemenu-group-container');
+							$group.addClass('collapsed').removeClass('expanded');
+							TYPO3.Backend.NavigationContainer.cleanup();
+							$groupContainer.hide().promise().done(function () {
+								TYPO3.Backend.doLayout();
+							});
+						}
+					});
+					me.initializeEvents();
 				});
-				me.initializeEvents();
 			},
 
 			initializeEvents: function () {
@@ -98,8 +111,11 @@ require(
 				// register clicking on sub modules
 				$(document).on('click', '.modulemenu-item,.t3-menuitem-submodule', function (evt) {
 					evt.preventDefault();
-					me.showModule($(this).attr('id'));
-					TYPO3.Backend.doLayout();
+					me.showModule(
+						$(this).attr('id'),
+						null,
+						evt
+					);
 				});
 				$(document).on('click', '.t3js-topbar-button-modulemenu',
 					function (evt) {
@@ -163,33 +179,71 @@ require(
 				};
 			},
 
-			showModule: function (mod, params) {
+			/**
+			 * @param {string} mod
+			 * @param {string} params
+			 * @param {Event} [event]
+			 * @return {jQuery.Deferred}
+			 */
+			showModule: function (mod, params, event) {
 				params = params || '';
 				params = this.includeId(mod, params);
 				var record = this.getRecordFromName(mod);
-				this.loadModuleComponents(record, params);
+				return this.loadModuleComponents(
+					record,
+					params,
+					new ClientRequest('typo3.showModule', event)
+				);
 			},
 
-			loadModuleComponents: function (record, params) {
+			/**
+			 * @param {object} record
+			 * @param {string} params
+			 * @param {InteractionRequest} [interactionRequest]
+			 * @return {jQuery.Deferred}
+			 */
+			loadModuleComponents: function (record, params, interactionRequest) {
 				var mod = record.name;
-				if (record.navigationComponentId) {
-					this.loadNavigationComponent(record.navigationComponentId);
-				} else if (record.navigationFrameScript) {
-					TYPO3.Backend.NavigationContainer.show('typo3-navigationIframe');
-					this.openInNavFrame(record.navigationFrameScript, record.navigationFrameScriptParam);
-				} else {
-					TYPO3.Backend.NavigationContainer.hide();
-				}
-
-				this.highlightModuleMenuItem(mod);
-				this.loadedModule = mod;
-				this.openInContentFrame(record.link, params);
-
-				// compatibility
-				top.currentSubScript = record.link;
-				top.currentModuleLoaded = mod;
 
-				TYPO3.Backend.doLayout();
+				var deferred = TYPO3.Backend.ContentContainer.beforeSetUrl(interactionRequest);
+				deferred.then(
+					$.proxy(function() {
+						if (record.navigationComponentId) {
+							this.loadNavigationComponent(record.navigationComponentId);
+						} else if (record.navigationFrameScript) {
+							TYPO3.Backend.NavigationContainer.show('typo3-navigationIframe');
+							this.openInNavFrame(
+								record.navigationFrameScript,
+								record.navigationFrameScriptParam,
+								new TriggerRequest(
+									'typo3.loadModuleComponents',
+									interactionRequest
+								)
+							);
+						} else {
+							TYPO3.Backend.NavigationContainer.hide();
+						}
+
+						this.highlightModuleMenuItem(mod);
+						this.loadedModule = mod;
+						this.openInContentFrame(
+							record.link,
+							params,
+							new TriggerRequest(
+								'typo3.loadModuleComponents',
+								interactionRequest
+							)
+						);
+
+						// compatibility
+						top.currentSubScript = record.link;
+						top.currentModuleLoaded = mod;
+
+						TYPO3.Backend.doLayout();
+					}, this
+				));
+
+				return deferred;
 			},
 
 			includeId: function (mod, params) {
@@ -231,23 +285,55 @@ require(
 				this.availableNavigationComponents[componentId] = initCallback;
 			},
 
-			openInNavFrame: function (url, params) {
+			/**
+			 * @param {string} url
+			 * @param {string} params
+			 * @param {InteractionRequest} [interactionRequest]
+			 * @return {jQuery.Deferred}
+			 */
+			openInNavFrame: function (url, params, interactionRequest) {
 				var navUrl = url + (params ? (url.indexOf('?') !== -1 ? '&' : '?') + params : '');
 				var currentUrl = TYPO3.Backend.NavigationContainer.getUrl();
+				var deferred = TYPO3.Backend.NavigationContainer.setUrl(
+					url,
+					new TriggerRequest('typo3.openInNavFrame', interactionRequest)
+				);
 				if (currentUrl !== navUrl) {
-					TYPO3.Backend.NavigationContainer.refresh();
+					// if deferred is already resolved, execute directly
+					if (deferred.state() === 'resolved') {
+						TYPO3.Backend.NavigationContainer.refresh();
+					// otherwise hand in future callback
+					} else {
+						deferred.then(TYPO3.Backend.NavigationContainer.refresh);
+					}
 				}
-				TYPO3.Backend.NavigationContainer.setUrl(url);
+				return deferred;
 			},
 
-			openInContentFrame: function (url, params) {
+			/**
+			 * @param {string} url
+			 * @param {string} params
+			 * @param {InteractionRequest} [interactionRequest]
+			 * @return {jQuery.Deferred}
+			 */
+			openInContentFrame: function (url, params, interactionRequest) {
+				var deferred;
+
 				if (top.nextLoadModuleUrl) {
-					TYPO3.Backend.ContentContainer.setUrl(top.nextLoadModuleUrl);
+					deferred = TYPO3.Backend.ContentContainer.setUrl(
+						top.nextLoadModuleUrl,
+						new TriggerRequest('typo3.openInContentFrame', interactionRequest)
+					);
 					top.nextLoadModuleUrl = '';
 				} else {
 					var urlToLoad = url + (params ? (url.indexOf('?') !== -1 ? '&' : '?') + params : '');
-					TYPO3.Backend.ContentContainer.setUrl(urlToLoad);
+					deferred = TYPO3.Backend.ContentContainer.setUrl(
+						urlToLoad,
+						new TriggerRequest('typo3.openInContentFrame', interactionRequest)
+					);
 				}
+
+				return deferred;
 			},
 
 			highlightModuleMenuItem: function (module, mainModule) {
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js b/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js
index afb2f50d634ab7da03938a1981fe6a91090cbb46..7b6aecc00fd2d877b0cea1f2a421a7a5df602574 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js
@@ -21,11 +21,26 @@ define(
 	[
 		'jquery',
 		'TYPO3/CMS/Backend/Icons',
+		'TYPO3/CMS/Backend/Event/ConsumerScope',
+		'TYPO3/CMS/Backend/Event/TriggerRequest'
 	],
-	function ($, Icons) {
+	function ($, Icons, ConsumerScope, TriggerRequest) {
 		'use strict';
 
+		function resolveIFrameElement() {
+			var $iFrame = $('.t3js-scaffold-content-module-iframe:first');
+			if ($iFrame.length === 0) {
+				return null;
+			}
+			return $iFrame.get(0);
+		}
+
 		TYPO3.Backend = {
+			/**
+			 * @type {ConsumerScope}
+			 */
+			consumerScope: ConsumerScope,
+
 			initialize: function() {
 				TYPO3.Backend.doLayout();
 				$(window).on('resize', TYPO3.Backend.doLayout);
@@ -88,15 +103,29 @@ define(
 					$('.t3js-scaffold-content-navigation [data-component]').hide();
 					$('.t3js-scaffold-content-navigation [data-component=' + component + ']').show();
 				},
-				setUrl: function(urlToLoad) {
-					$('.t3js-scaffold').addClass('scaffold-content-navigation-expanded');
-					$('.t3js-scaffold-content-navigation-iframe').attr('src', urlToLoad);
+				/**
+				 * @param {string} urlToLoad
+				 * @param {InteractionRequest} [interactionRequest]
+				 * @return {jQuery.Deferred}
+				 */
+				setUrl: function(urlToLoad, interactionRequest) {
+					var deferred = TYPO3.Backend.consumerScope.invoke(
+						new TriggerRequest('typo3.setUrl', interactionRequest)
+					);
+					deferred.then(function() {
+						$('.t3js-scaffold').addClass('scaffold-content-navigation-expanded');
+						$('.t3js-scaffold-content-navigation-iframe').attr('src', urlToLoad);
+					});
+					return deferred;
 				},
 				getUrl: function() {
 					return $('.t3js-scaffold-content-navigation-iframe').attr('src');
 				},
-				refresh: function() {
-					$('.t3js-scaffold-content-navigation-iframe')[0].contentWindow.location.reload();
+				/**
+				 * @param {boolean} forceGet
+				 */
+				refresh: function(forceGet) {
+					$('.t3js-scaffold-content-navigation-iframe')[0].contentWindow.location.reload(forceGet);
 				},
 				calculateScrollbar: function (){
 					TYPO3.Backend.NavigationContainer.cleanup();
@@ -125,19 +154,66 @@ define(
 				get: function() {
 					return $('.t3js-scaffold-content-module-iframe')[0].contentWindow;
 				},
-				setUrl: function (urlToLoad) {
-					TYPO3.Backend.Loader.start();
-					$('.t3js-scaffold-content-module-iframe')
-						.attr('src', urlToLoad)
-						.one('load', function() {
-							TYPO3.Backend.Loader.finish();
-						});
+				/**
+				 * @param {InteractionRequest} [interactionRequest]
+				 * @return {jQuery.Deferred}
+				 */
+				beforeSetUrl: function(interactionRequest) {
+					return TYPO3.Backend.consumerScope.invoke(
+						new TriggerRequest('typo3.beforeSetUrl', interactionRequest)
+					);
+				},
+				/**
+				 * @param {String} urlToLoad
+				 * @param {InteractionRequest} [interactionRequest]
+				 * @return {jQuery.Deferred}
+				 */
+				setUrl: function (urlToLoad, interactionRequest) {
+					var deferred;
+					var iFrame = resolveIFrameElement();
+					// abort, if no IFRAME can be found
+					if (iFrame === null) {
+						deferred = $.Deferred();
+						deferred.reject();
+						return deferred;
+					}
+					deferred = TYPO3.Backend.consumerScope.invoke(
+						new TriggerRequest('typo3.setUrl', interactionRequest)
+					);
+					deferred.then(function() {
+						TYPO3.Backend.Loader.start();
+						$('.t3js-scaffold-content-module-iframe')
+							.attr('src', urlToLoad)
+							.one('load', function() {
+								TYPO3.Backend.Loader.finish();
+							});
+					});
+					return deferred;
 				},
 				getUrl: function() {
 					return $('.t3js-scaffold-content-module-iframe').attr('src');
 				},
-				refresh: function() {
-					$('.t3js-scaffold-content-module-iframe')[0].contentWindow.location.reload();
+				/**
+				 * @param {boolean} forceGet
+				 * @param {InteractionRequest} interactionRequest
+				 * @return {jQuery.Deferred}
+				 */
+				refresh: function(forceGet, interactionRequest) {
+					var deferred;
+					var iFrame = resolveIFrameElement();
+					// abort, if no IFRAME can be found
+					if (iFrame === null) {
+						deferred = $.Deferred();
+						deferred.reject();
+						return deferred;
+					}
+					deferred = TYPO3.Backend.consumerScope.invoke(
+						new TriggerRequest('typo3.refresh', interactionRequest)
+					);
+					deferred.then(function() {
+						iFrame.contentWindow.location.reload(forceGet);
+					});
+					return deferred;
 				},
 				getIdFromUrl: function() {
 					if(this.getUrl) {
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/actions.js b/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/actions.js
index fbba6a3adcd4e8b0b0f663588866e82c3fb670e9..3e640de39205efd2a13b186774f6d7060d4b0f61 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/actions.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/actions.js
@@ -313,7 +313,6 @@ TYPO3.Components.PageTree.Actions = {
 	 * @return {void}
 	 */
 	editPageProperties: function(node) {
-		node.select();
 		var returnUrl = TYPO3.Backend.ContentContainer.getUrl();
 		if (returnUrl.indexOf('returnUrl') !== -1) {
 			returnUrl = TYPO3.Utility.getParameterFromUrl(returnUrl, 'returnUrl');
@@ -329,6 +328,8 @@ TYPO3.Components.PageTree.Actions = {
 
 		TYPO3.Backend.ContentContainer.setUrl(
 			TYPO3.settings.FormEngine.moduleUrl + '&edit[pages][' + node.attributes.nodeData.id + ']=edit&returnUrl=' + returnUrl
+		).then(
+			node.select
 		);
 	},
 
@@ -339,9 +340,10 @@ TYPO3.Components.PageTree.Actions = {
 	 * @return {void}
 	 */
 	newPageWizard: function(node) {
-		node.select();
 		TYPO3.Backend.ContentContainer.setUrl(
 			TYPO3.settings.NewRecord.moduleUrl + '&id=' + node.attributes.nodeData.id + '&pagesOnly=1'
+		).then(
+			node.select
 		);
 	},
 
@@ -362,9 +364,10 @@ TYPO3.Components.PageTree.Actions = {
 	 * @return {void}
 	 */
 	openHistoryPopUp: function(node) {
-		node.select();
 		TYPO3.Backend.ContentContainer.setUrl(
 			TYPO3.settings.RecordHistory.moduleUrl + '&element=pages:' + node.attributes.nodeData.id
+		).then(
+			node.select
 		);
 	},
 
@@ -375,13 +378,14 @@ TYPO3.Components.PageTree.Actions = {
 	 * @return {void}
 	 */
 	exportT3d: function(node) {
-		node.select();
 		TYPO3.Backend.ContentContainer.setUrl(
 			TYPO3.settings.ImportExport.moduleUrl +
 			'&tx_impexp[action]=export&' +
 			'id=0&tx_impexp[pagetree][id]=' + node.attributes.nodeData.id +
 			'&tx_impexp[pagetree][levels]=0' +
 			'&tx_impexp[pagetree][tables][]=_ALL'
+		).then(
+			node.select
 		);
 	},
 
@@ -392,11 +396,12 @@ TYPO3.Components.PageTree.Actions = {
 	 * @return {void}
 	 */
 	importT3d: function(node) {
-		node.select();
 		TYPO3.Backend.ContentContainer.setUrl(
 			TYPO3.settings.ImportExport.moduleUrl +
 			'&id=' + node.attributes.nodeData.id +
 			'&table=pages&tx_impexp[action]=import'
+		).then(
+			node.select
 		);
 	},
 
@@ -709,24 +714,22 @@ TYPO3.Components.PageTree.Actions = {
 	 * @return {void}
 	 */
 	singleClick: function(node, tree) {
-		tree.currentSelectedNode = node;
-
 		var separator = '?';
 		if (currentSubScript.indexOf('?') !== -1) {
 			separator = '&';
 		}
 
-		node.select();
-		if (tree.stateHash) {
-			tree.stateHash.lastSelectedNode = node.id;
-		}
-
-		fsMod.recentIds['web'] = node.attributes.nodeData.id;
-		fsMod.recentIds['system'] = node.attributes.nodeData.id;
-
 		TYPO3.Backend.ContentContainer.setUrl(
 			currentSubScript + separator + 'id=' + node.attributes.nodeData.id
-		);
+		).then(function() {
+			node.select();
+			tree.currentSelectedNode = node;
+			if (tree.stateHash) {
+				tree.stateHash.lastSelectedNode = node.id;
+			}
+			fsMod.recentIds['web'] = node.attributes.nodeData.id;
+			fsMod.recentIds['system'] = node.attributes.nodeData.id;
+		});
 	},
 
 	/**
@@ -742,13 +745,14 @@ TYPO3.Components.PageTree.Actions = {
 			return;
 		}
 
-		node.select();
 		var nodeId = node.attributes.nodeData.id,
 			idPattern = '###ID###';
 		TYPO3.Backend.ContentContainer.setUrl(
 			contextItem.customAttributes.contentUrl
 				.replace(idPattern, nodeId)
 				.replace(encodeURIComponent(idPattern), nodeId)
+		).then(
+			node.select
 		);
 	},
 
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-77268-IntroduceJavaScriptTriggerRequestAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-77268-IntroduceJavaScriptTriggerRequestAPI.rst
new file mode 100644
index 0000000000000000000000000000000000000000..3ba07876e1269a634305fd858f6f51e63dd7a977
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-77268-IntroduceJavaScriptTriggerRequestAPI.rst
@@ -0,0 +1,97 @@
+.. include:: ../../Includes.txt
+
+==========================================================
+Feature: #77268 - Introduce JavaScript trigger request API
+==========================================================
+
+See :issue:`77268`
+
+Description
+===========
+
+JavaScript event handling the backend of the TYPO3 core is based on the optimistic
+assumption, that most executions can be executed sequentially and are processed
+just in time. This concept does not consider the fact that other nested components
+can defer the execution based on additional user input e.g. as used in confirmation
+dialogs.
+
+That's why a trigger request API is introduced to first inform dependent components
+about a planned action which will defer the regular execution based on specific
+application state logic of registered components. In the current implementation,
+FormEngine's edit forms register themselves to be notified, thus accidentally
+closing modified forms by clicking e.g. the module menu any other page in the
+page tree can be handled.
+
+Registering component
+~~~~~~~~~~~~~~~~~~~~~
+
+The following code attaches or detaches a particular component (a **consumer**)
+to be notified.
+
+.. code-block:: javascript
+
+	// FormEngine must implement the Consumable interface,
+        // thus having a function named consume(interactionRequest)
+	top.TYPO3.Backend.consumerScope.attach(FormEngine);
+	top.TYPO3.Backend.consumerScope.detach(FormEngine);
+
+Invoking consumers
+~~~~~~~~~~~~~~~~~~
+
+Registered consumers are invoked with a specific interaction request that has a
+defined action type and optionally additional information about the parent call
+(e.g. some client event issued by users). Invocations return a jQuery.Deferred()
+object that resolves when no consumers are registered or every consumer sends a
+resolve command as well - if only one consumer rejects, the collective invocation
+promise is rejected as well.
+
+.. code-block:: javascript
+
+	var deferred = TYPO3.Backend.consumerScope.invoke(
+		new TriggerRequest('typo3.setUrl', interactionRequest)
+	);
+	deferred
+		.then(function() { console.log('consumers are resolved'); })
+		.fail(function() { console.log('some consumer was rejected'); });
+
+Creating interaction requests
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Currently there are two types of reuqests, `ClientRequest` that is based on some
+client event (e.g. `click` event) and `TriggerRequest` which may be based on some
+parent request of type `InteractionRequest` - this is used to cascade actions.
+
+.. code-block:: javascript
+
+   var clickRequest = new ClientRequest('typo3.showModule', event);
+   var triggerRequestA = new TriggerRequest('typo3.a', clickRequest);
+   var triggerRequestB = new TriggerRequest('typo3.b', triggerRequestA);
+
+In the example `triggerRequestB` has all information from the initial click
+event down to the specific `typo3.b` action type. The first request can be
+resolved from the most specific request by `triggerRequestB.outerMostRequest`
+and will return `clickRequest` in this case.
+
+Working with interaction requests
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
++ `triggerRequestB.concerns(clickRequest)` checks whether `clickRequest` is an
+  ancestor request in the cascade of `triggerRequestB` (which is true, based on
+  the previous example)
++ `triggerRequestB.concernsType('typo3.showModule')` checks whether `typo3.showModule`
+  is the type of some ancestor request in the cascade of `triggerRequestB` (which
+  is true, based on the previous example)
++ `triggerRequestB.outerMostRequest.setProcessedData({response: true})` sets the
+  property evaluated by `clickRequest.isProcessed()` to `true` and stores any
+  custom user response (e.g. from some confirmation dialog) at the outer-most
+  interaction request
+
+Impact
+======
+
+Using interaction requests requires some modifications in the JavaScript processing
+logic which changes from sequential processing to possibly deferred asynchronous
+processing. This is required since e.g. user input is required first to be able
+to continue the processing. The created promises are based on `jQuery.Deferred`.
+
+.. index:: Backend, JavaScript
diff --git a/typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js b/typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js
index 1cf258657ba84cf4c6f1b769407a390479e8cacb..799bb6615a4f668873db9165d48fc49e4a87cd4d 100644
--- a/typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js
+++ b/typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js
@@ -109,7 +109,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
         var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
         url += '&CB[el][_FILE%7C' + shortMD5 + ']=' + top.rawurlencode(uid) + '&CB[setCopyMode]=1';
         $.ajax(url).always(function () {
-            top.list_frame.location.reload(true);
+            top.TYPO3.Backend.ContentContainer.refresh(true);
         });
     };
 
@@ -118,7 +118,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
         var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
         url += '&CB[el][_FILE%7C' + shortMD5 + ']=0&CB[setCopyMode]=1';
         $.ajax(url).always(function () {
-            top.list_frame.location.reload(true);
+            top.TYPO3.Backend.ContentContainer.refresh(true);
         });
     };
 
@@ -127,7 +127,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
         var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
         url += '&CB[el][_FILE%7C' + shortMD5 + ']=' + top.rawurlencode(uid);
         $.ajax(url).always(function () {
-            top.list_frame.location.reload(true);
+            top.TYPO3.Backend.ContentContainer.refresh(true);
         });
     };
 
@@ -136,7 +136,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func
         var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard'];
         url += '&CB[el][_FILE%7C' + shortMD5 + ']=0';
         $.ajax(url).always(function () {
-            top.list_frame.location.reload(true);
+            top.TYPO3.Backend.ContentContainer.refresh(true);
         });
     };