Focus input text field on Esc

Focus the first visible input text field when you press Esc key, or restore the previously focused element on second press

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name          Focus input text field on Esc
// @description   Focus the first visible input text field when you press Esc key, or restore the previously focused element on second press
// @version       1.0.11
// @include       *
// @author        wOxxOm
// @namespace     wOxxOm.scripts
// @license       MIT License
// @run-at        document-start
// @grant         none
// ==/UserScript==

var TEXT_FIELD = ' search text number url textarea ';
var previousElement;
var scrollPos;
var first;

window.addEventListener('keydown', function (e) {
  if (e.defaultPrevented || e.which !== 27 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
    return;
  }
  if (window !== top) {
    rememberFocus();
    window.addEventListener('message', maybeRestoreFocus);
    top.postMessage(GM_info.script.name, '*');
    e.preventDefault();
    e.stopPropagation();
    return;
  }
  run();
}, true);

if (window === top) {
  window.addEventListener('message', function (e) {
    if (e.data === GM_info.script.name) {
      run({relayedFromFrame: true});
    }
  });
}

function run(params) {
  // find text inputs inside visible DOM containers
  var inputs = [];
  populateInputs(inputs);
  for (var i = 0, input, il = inputs.length; i < il && (input = inputs[i]); i++) {
    var priority = TEXT_FIELD.indexOf(' ' + input.type + ' ');
    if (priority < 0) continue;
    var n = input, style;
    while (n && n.style && (style = getComputedStyle(n)) && style.display !== 'none' && style.visibility !== 'hidden') {
      n = n.parentNode;
    }
    // visible if reached DOM root
    if (n && n.style) continue;
    // set the first OR if it's empty, try to select an identically named input field with some text (happens on some sites)
    if (!first || (
      input.value &&
      input.name === first.name && (
        !input.form && !first.form ||
        input.form && first.form && input.form.action === first.form.action
      )
    )) {
      first = input;
      if (first.value) break;
    }
  }

  if (!first) return;

  var invoke = params && params.relayedFromFrame ? passthru : onkeyup;

  if (first !== getActiveElement()) {
    rememberFocus();
    invoke(setFocus);
  } else if (previousElement) {
    invoke(restoreFocus);
    if (previousElement && previousElement.localName === 'iframe') {
      previousElement.contentWindow.postMessage(GM_info.script.name, '*');
    }
  }
}

function populateInputs(inputs, root) {
  var walker = document.createTreeWalker(root || document, NodeFilter.SHOW_ELEMENT);
  var el;
  while ((el = walker.nextNode())) {
    if (el.shadowRoot)
      populateInputs(inputs, el.shadowRoot);
    if (/^(input|textarea)$/.test(el.localName))
      inputs.push(el);
  }
}

function getActiveElement() {
  var el = document.activeElement;
  while (el) {
    if (!el.shadowRoot)
      return el;
    el = el.shadowRoot.activeElement;
  }
}

function rememberFocus() {
  previousElement = document.activeElement;
  scrollPos = [scrollX, scrollY];
}

function setFocus() {
  first.focus();
}

function restoreFocus() {
  // in case document.body (page "background") was previously selected
  document.activeElement.blur();
  previousElement.focus();
  scrollTo(scrollPos[0], scrollPos[1]);
}

function maybeRestoreFocus(e) {
  if (e.data === GM_info.script.name) {
    restoreFocus();
  }
}

// focusing should be done at key-up to prevent the Esc-keydown being also chain-handled by the just focused element
function onkeyup(cb) {
  window.addEventListener('keyup', function keyup(e) {
    if (e.which !== 27) return;
    window.removeEventListener('keyup', keyup);
    if (e.defaultPrevented) return;
    cb(e);
  });
}

function passthru(fn) {
  return fn.apply(this, arguments);
}