4

I'm trying to force a synchronous repaint. Is this possible?

Unfortunately, the library I am working with sends a long-running synchronous request via XMLHttpRequest.prototype.open(_, _, false). I am trying to update the page with a loading indicator before the request is sent by monkey patching XMLHttpRequest.prototype.send, however, the opacity: 0.7 style is never seen:

const oldSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(...args) {
  document.body.style.opacity = "0.7";

  // is there something I can do to force a repaint on this line?

  const retVal = oldSend.bind(this)(...args);
  document.body.style.opacity = "1";
  return retVal;
}

Most of my research suggests refactoring to something asynchronous:

However, I don't have control over the code making the synchronous request.

EDIT: another related post:

uber5001
  • 3,864
  • 4
  • 23
  • 44
  • 1
    Just fyi, `oldSend.bind(this)(...args)` could be `oldSend.apply(this, args)` – 4castle Feb 15 '18 at 22:20
  • Since you can monkey-patch this function, why not alter your dom, then call the original slow synchronous function on a short `setTimeout`? Looking forward to seeing "right" way of doing this :-). – Joshua R. Feb 15 '18 at 22:22
  • 1
    No, this is not possible. You can force a reflow, but not a repaint. And btw, cases like this are exactly why SJAX is deprecated. – Bergi Feb 15 '18 at 22:26
  • @JoshuaR. The library makes many, many of these requests, and they are generally not triggered by something I call. I don't think I have a way to delay the request. – uber5001 Feb 15 '18 at 22:27
  • 2
    @JoshuaR. Had the same idea, and it would work for `send` in particular since it returns `undefined` anyway, but then the response would be immediately available to the caller. – Bergi Feb 15 '18 at 22:28
  • yeah, that's true... with that in mind, there's no solution other than finding a better lib – Kevin B Feb 15 '18 at 22:29
  • Which library is it? You’ll probably just need to fix it. – Ry- Feb 15 '18 at 22:39
  • @Ryan It's some proprietary software (not ours). I don't have any access to any of the source. – uber5001 Feb 15 '18 at 22:43

2 Answers2

2

Explanation of why screen updates are not possible in the middle of executing synchronous code may be found in the HTML spec:

8.1.4.1 Definitions

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section.

which implies rendering is a separate call out from the event loop in relation to call outs that execute scripts.

This is further detailed under the processing model:

8.1.4.2 Processing model

.... pick a task and run it [ steps 1-6]

  1. Update the rendering: If this event loop is a browsing context event loop (as opposed to a worker event loop), then run the following substeps.

....

Hence it is not possible to update the screen from within a browsing context in the middle of executing synchronous code in a single call out from the event loop.

Moving synchronous code to a web worker might form the basis of a solution (workers execute in a separate thread) but is outside the scope of this answer.

Community
  • 1
  • 1
traktor
  • 17,588
  • 4
  • 32
  • 53
  • I don't think this would prevent an API that allows manually invoking these steps from a script, but yeah it's not designed to need one. – Bergi Feb 16 '18 at 00:47
1

It looks like this is not possible, based on comments on the question, research, and my own experience.

For my use case, I'll make the page "freezing" the loading indicator, by having something on the page that is always animated. Or, more likely, just forgo the loading indicator feature.

uber5001
  • 3,864
  • 4
  • 23
  • 44
  • 1
    If anyone has concrete evidence that a synchronous repaint is not possible, post that as an answer, and I will accept it. – uber5001 Feb 15 '18 at 22:44