ie9-oninput-polyfill icon indicating copy to clipboard operation
ie9-oninput-polyfill copied to clipboard

Infinite event loop

Open agka opened this issue 5 years ago • 4 comments

Because the polyfill listen to any selectionChange event to trigger a synthetic input event, if one is to call setSelectionRange() from within a input event handler, then an infinite event loop occurs (setSelectionRange() does trigger a selectionChange).

My take, cause I'm aware that this project is not really alive, and also too lazy to do a proper PR

(function ($nav, $doc) {
    "use strict";

    var $targetInput;

    function isTextField($elem) {
        return $elem.tagName === "TEXTAREA" || ($elem.tagName === "INPUT" && $elem.type === "text");
    }

    // using bitwise NOT to compare against -1 as  ~(-1) returns 0
    // (~indexOf) is similar to (indexOf != -1)
    if (~$nav.userAgent.indexOf("MSIE 9")) {
        $doc.addEventListener("keydown", function (e) {
            // 8    backspace
            // 46   delete
            if (isTextField(e.target) && ~[8, 46].indexOf(e.which)) {
                $targetInput = e.target;
            }
        });

        $doc.addEventListener("cut", function (e) {
            // Note: cut is triggered before the action
            if (isTextField(e.target)) {
                $targetInput = e.target;
            }
        });

        $doc.addEventListener("selectionchange", function () {
            var event;

            if ($targetInput && $targetInput === $doc.activeElement) {
                event = $doc.createEvent("CustomEvent");
                event.initCustomEvent("input", true, false, {});
                $targetInput.dispatchEvent(event);
                $targetInput = null;
            }
        });
    }
})(navigator, document);

agka avatar Mar 25 '19 14:03 agka

It should be simple to just check if (!e.isTrusted) return before anything else.

buzinas avatar Mar 25 '19 15:03 buzinas

Something like:

(function (d) {
  if (navigator.userAgent.indexOf('MSIE 9') === -1) return;

  d.addEventListener('selectionchange', function(e) {
    if (!e.isTrusted) return;
    var el = d.activeElement;

    if (el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && el.type === 'text')) {
      var ev = d.createEvent('CustomEvent');
      ev.initCustomEvent('input', true, true, {});
      el.dispatchEvent(ev);
    }
  });
})(document);

buzinas avatar Mar 25 '19 15:03 buzinas

Can you try it? @agka

buzinas avatar Mar 25 '19 15:03 buzinas

Thanks for the quick response.

Alas, that does not the trick. But for other reasons.

If you were to change the value of an input, and then update the caret position within the same field, IE9 can't do it properly during the same "frame"

In the input event handler I had something like

  input.value = strVal;
  input.setSelectionRange(caretPos, caretPos);

My guess : IE9 resets the input value to "" first, then somewhat schedules a value change later. Because the input value is empty input.setSelectionRange() moves the caret to the end of the empty string. When IE9 applies the value, it also remembers to move the caret, but it is moved to the end of the string value.

The behaviour has been fixed in later IE versions

A workaround is to use setTimeout() to delay the call to setSelectionRange()

  input.value = strVal;
  setTimeout(function () {
    input.setSelectionRange(caretPos, caretPos);
  }, 0);

But calling setSelectionRange() on an input also force the focus back to the input field.

And the last straw, switching focus between inputs does trigger selectionChange events, before any focus or blur events (I checked)

So, with the suggested change, whenever the focus is switched away from an text input, a selectionchange is fired, which triggers a synthetic input (ok) But because my input handler delays a setSelectionRange(), the focus is put right back to the text input, thus the user is "trapped".

agka avatar Mar 25 '19 16:03 agka