isInputPending

This document describes an API for detecting if there are pending input events that script might be delaying from firing by continuing to run.

This document is in the draft stage and has yet to be submitted to a working group for approval.

Introduction

When running script that's required to display something there is a trade off that developers need to make today. If a script might take a long time to run and a user provides some sort of input while this is happening, then the user agent would need to wait until the script has finished before dispatching the input event. Having a lot of lag before responding to an input event is not a great user experience, so developers will often break up long scripts into smaller chunks in order to allow the user agent to dispatch events between the chunks. Each time the script yields it will need to somehow post a message, call a combination of requestAnimation frame and requestIdleCallback, or do something else in order to make sure it both truly yields to allow for events to be dispatched and then that it can get called again after yielding. Even in the best case scenario, each time the script yields it can take many milliseconds before it gets a chance to run again. So unfortunately, this is also not a great user experience since in part now the initial script gets delayed by quite a bit since it needs to yield so much.

Without isInputPending there can be a tradeoff between loading pages quickly and responding to input quickly.
Without isInputPending there can be a tradeoff between loading pages quickly and responding to input quickly.

The goal of the isInputPending api is that it will now allow developers to eliminate this trade off. Instead of totally yielding to the user agent and having to incur the cost of one or multiple event loops after yielding, long running script can now run to its completion while still remaining responsive.

isInputPending can be used to remove any tradeoff between loading pages quickly and responding to input quickly.
isInputPending can be used to remove any tradeoff between loading pages quickly and responding to input quickly.

Examples of Usage

If a developer has a list of tasks that need executing, then adoption is quite simple.

        let taskQueue = [task1, task2, ...];

        function doWork() {
          while (let task = taskQueue.pop()) {
            task.execute();
            if (navigator.scheduling.isInputPending(['click', 'keydown', ...])) {
              setTimeout(doWork, 0);
              break;
            }
          }
        }

        doWork();
      

InputType

The InputType type represents a DOMString that MAY be equal to an event type.

        typedef DOMString InputType;
      

The Scheduling interface

        [Exposed=Window] interface Scheduling {
           boolean isInputPending(optional sequence<InputType> inputTypes = []);
           boolean canCheckInputPending(optional InputType inputType = "");
        };
      

The isInputPending Method

The isInputPending method returns a value based on the strings listed in inputTypes. If the isInputPending method is called then the user agent MUST run the following steps:

  1. If inputTypes is empty then let inputTypes be an array of SupportedEventTypes.
  2. Let pendingResult be false.
  3. The user agent MAY at this step set pendingResult to true for any reason.
  4. The user agent MAY at this step continue to step 6 for any reason.
  5. For each item in inputTypes:
    1. If item is not in SupportedEventTypes, continue to next item.
    2. If the surrounding agent's event loop has any task queues that contain a task that will dispatch an event, with type attribute equal to item, or if the user agent determines that it MAY soon enqueue such a task, and the dispitached event would have an EventTarget that exists within a window of the same origin, the user agent MAY let pendingResult be true and continue to step 6.
  6. Return pendingResult.

The overall goal for this api is to allow user agents to return true if they think an event may end up getting dispatched to a browsing context soon. Not only if an event is already placed into the queue. This may mean that a user agent may look into the current configuration of iframes, other events in the queue, or other information when making decisions in step 5b. For example, there may be cases where if a mousedown event is queued then a click event may be fired for this frame after the mousedown event. However after the mousedown event is fired the user agent knows that the click event will not be fired. In this case the user agent should return 'true' for a 'click' input before the mousedown event, but after it knows that click will not be fired it should return false.

The specification for the isInputPending method is intentionally written in such a way that a user agent may return true or false for any reason. This is so user agents may implement their own security measures while implementing this api and remain compliant with this specification.

The canCheckInputPending method

The canCheckInputPending method returns a value indicating if this api is supported in this environment and optionally if a specific InputType is supported. If the canCheckInputPending is called then the user agent MUST run the following steps:

  1. If SupportedEventTypes is empty return false.
  2. If inputType is not equal to an empty string and if inputType is not in SupportedEventTypes, return false.
  3. Return true.

Extensions to the Window interface

        partial interface Window {
          readonly attribute Scheduling scheduling;
        };
      

SupportedEventTypes

SupportedEventTypes is an internal array of event types that the user agent supports detecting pending events for. A user agent MUST not expose SupportedEventTypes to script.

SupportedEventTypes MUST include:

SupportedEventTypes MAY include: Any UIEvent, MediaCapture Event or any other event type.

Privacy and Security

Script can already monitor for events within same origin browsing contexts. A child iframe with the same origin as the parent can already observe timing of events on the parent iframe and vice versa due to the fact that they share an event loop. So from that standpoint, this spec does not introduce a new mechanism for monitoring events. However, practically, when user agents implement this specification it may be challenging to isolate the event queues they check to just the current same origin browsing contexts, this may be partially difficult in user agents that do not use a separate process for iframes that have dissimilar origin. Allowing an origin to monitor events on another origin could allow for a range of attacks. User agents are encouraged to think about how to mitigate the risk of leaking events from dissimilar origins and the specification is written in such a way that user agents may return true or false for any reason from isInputPending in order to complicate such attacks.

The case of a child iframe within a frame is even trickier to deal with, as the frame that gets an event may change based on what script is currently doing. In some cases a malicious cross origin frame could attempt to bring focus to itself in order to look for input events that a user could have intended to be sent to the parent frame. For example, a malicious origin could attempt to get a user to click on a child iframe by moving it around the screen, some user agents mitigate this by discarding input events on recently moved frames. User agents should pay special attention to this case and add appropriate countermeasures to their implementations of this api based on their architecture.

One thing user agents may want to consider is a combination of off-main-thread based hit testing (e.g. done on the compositor thread, if present) and keyboard focus to determine the appropriate document to mark as having input pending. By performing hit testing online, calls to isInputPending can be made faster, safer from timing attacks, and thus more effective for scheduling purposes.