Skip to content
Snippets Groups Projects
Commit 45d8e094 authored by Benjamin Franzke's avatar Benjamin Franzke
Browse files

[BUGFIX] Avoid race condition in DocumentService.ready()

Improve document-service responsiveness by relying on
`DOMContentLoaded` and `document.readyState` >= `interactive`.

1) Handle non-loading state as "ready" to avoid waiting for `complete`.

   `document.readyState` has three states:

    * `loading`
       The document is still loading.

    * `interactive`
       The document has finished loading and the document
       has been parsed but sub-resources such as scripts, images,
       stylesheets and frames are still loading. The state indicates
       that the DOMContentLoaded event is about to fire.

    * `complete`
       The document and all sub-resources have finished loading.
       The state indicates that the load event is about to fire.

   If DocumentService.ready was called in "interactive" state we have
   been skipping this state as we only considered `complete` to be
   the "ready" state in this case.
   This is wrong as we actually wait for the DOMContentLoaded if we
   are launche...
parent 3f634328
No related merge requests found
......@@ -16,42 +16,18 @@
* @exports TYPO3/CMS/Core/DocumentService
*/
class DocumentService {
private readonly windowRef: Window;
private readonly documentRef: Document;
private promise: Promise<Document> = null;
/**
* @param {Window} windowRef
* @param {Document} documentRef
*/
constructor(windowRef: Window = window, documentRef: Document = document) {
this.windowRef = windowRef;
this.documentRef = documentRef;
public ready(): Promise<Document> {
return this.promise ?? (this.promise = this.createPromise());
}
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);
}
});
private async createPromise(): Promise<Document> {
if (document.readyState !== 'loading') {
return document;
}
await new Promise<void>(resolve => document.addEventListener('DOMContentLoaded', () => resolve(), { once: true }));
return document;
}
}
......
......@@ -10,4 +10,4 @@
*
* 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
define(["require","exports"],(function(e,t){"use strict";return new class{constructor(){this.promise=null}ready(){var e;return null!==(e=this.promise)&&void 0!==e?e:this.promise=this.createPromise()}async createPromise(){return"loading"!==document.readyState||await new Promise(e=>document.addEventListener("DOMContentLoaded",()=>e(),{once:!0})),document}}}));
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment