Don’t resolve a Promise in idle()
authorTim Düsterhus <duesterhus@woltlab.com>
Sun, 25 Nov 2018 20:49:01 +0000 (21:49 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Thu, 29 Nov 2018 14:42:23 +0000 (15:42 +0100)
The `.then` will be executed in the next iteration of the event loop
and the browser might not be idle any longer when this happens.

Instead use a function that “idleify”s a function. When the returned function
is called it returns a `Promise` and synchronously executes the function when
the browser is idle.

see #2752

wcfsetup/install/files/js/WoltLabSuite/Core/Bbcode/Code.js

index bbfdf37351375feee43801994f73923e5e470112..daf77966d569da572ee6bc1c36cebe90dd773501 100644 (file)
@@ -11,23 +11,29 @@ define(['WoltLabSuite/Core/Prism', 'prism/prism-meta'], function(Prism, PrismMet
        
        /** @const */ var CHUNK_SIZE = 50;
        
-       // Define idle() for piecewiese highlighting to not block the UI thread.
-       var idle = function (value) {
-               return new Promise(function (resolve, reject) {
-                       setTimeout(function () {
-                               resolve(value);
-                       }, 0);
-               });
-       };
-       if (window.requestIdleCallback) {
-               idle = function (value) {
+       // Define idleify() for piecewiese highlighting to not block the UI thread.
+       var idleify = function (callback) {
+               return function () {
+                       var args = arguments;
                        return new Promise(function (resolve, reject) {
-                               window.requestIdleCallback(function () {
-                                       resolve(value);
-                               }, { timeout: 5000 });
+                               var body = function () {
+                                       try {
+                                               resolve(callback.apply(null, args));
+                                       }
+                                       catch (e) {
+                                               reject(e);
+                                       }
+                               };
+                               
+                               if (window.requestIdleCallback) {
+                                       window.requestIdleCallback(body, { timeout: 5000 });
+                               }
+                               else {
+                                       setTimeout(body, 0);
+                               }
                        });
                };
-       }
+       };
        
        /**
         * @constructor
@@ -62,10 +68,7 @@ define(['WoltLabSuite/Core/Prism', 'prism/prism-meta'], function(Prism, PrismMet
                        this.container.classList.add('highlighting');
                        
                        return require(['prism/components/prism-' + PrismMeta[this.language].file])
-                       .then(function () {
-                               return idle();
-                       })
-                       .then(function () {
+                       .then(idleify(function () {
                                var highlighted = Prism.highlightSeparateLines(this.codeContainer.textContent, this.language);
                                var highlightedLines = elBySelAll('[data-number]', highlighted);
                                var originalLines = elBySelAll('.codeBoxLine > span', this.codeContainer);
@@ -76,16 +79,16 @@ define(['WoltLabSuite/Core/Prism', 'prism/prism-meta'], function(Prism, PrismMet
                                
                                var promises = [];
                                for (var chunkStart = 0, max = highlightedLines.length; chunkStart < max; chunkStart += CHUNK_SIZE) {
-                                       promises.push(idle(chunkStart).then(function (chunkStart) {
+                                       promises.push(idleify(function (chunkStart) {
                                                var chunkEnd = Math.min(chunkStart + CHUNK_SIZE, max);
                                                
                                                for (var offset = chunkStart; offset < chunkEnd; offset++) {
                                                        originalLines[offset].parentNode.replaceChild(highlightedLines[offset], originalLines[offset]);
                                                }
-                                       }));
+                                       })(chunkStart));
                                }
                                return Promise.all(promises);
-                       }.bind(this))
+                       }.bind(this)))
                        .then(function () {
                                this.container.classList.remove('highlighting');
                                this.container.classList.add('highlighted');