JonLuca's Blog

01 Apr 2020

Re-enabling context actions on websites

A disappointing trend I’ve seen recently has been for sites disabling context actions like copy, paste, select, or right click on their websites. Banks are notoriously bad at this - for some reason, they think it’s a “security feature” to not let you paste in your account number, instead requiring you to manually type it in.

I realized it should be fairly easy to set up some global event listeners in the page and prevent the overridden behavior.

/**
 * Stops propagation of an event to other event listeners, while still allowing the event to complete in its native
 * context
 * @param {string} type - the event type, i.e. 'paste', 'keyup', etc
 */
function stopPropagationOfType(type) {
  window.addEventListener(
    type,
    function (event) {
      event.stopPropagation();
    },
    true
  );
}

If you inject this into the page with stopPropagationOfType('paste');, it will prevent any paste event listeners from running (which probably call e.preventDefault().

I created a chrome extension called PasteEnabler that you can use that reenables the following features:

  • Ability to paste content
  • Ability to copy content
  • Ability to cut content
  • Ability to right click content
  • Ability to autocomplete certain inputs
  • Ability to select text
  • Ability to drag and drop text to/from inputs

The code is fairly straightforward, and has worked on every site I’ve tried.

/**
 * Function invoked on every load of the script
 */
function checkCopyAndPaste() {
  // enables paste on all items
  stopPropagationOfType("paste");
  // enables copying any inputs or text
  stopPropagationOfType("copy");
  // enables cutting text from an input
  stopPropagationOfType("cut");
  // enables drag + drop of text into input
  stopPropagationOfType("drop");
  // Enables autocomplete on all elements that have it
  const autocompleteDisabled = document.querySelectorAll("[autocomplete]");
  for (const elem of autocompleteDisabled) {
    elem.setAttribute("autocomplete", "on");
  }

  // enables right click context menu
  stopPropagationOfType("contextmenu");

  // Finds all elements and adds the user-select CSS property as text
  // note - this is a *little* hacky, but it's the only way I've found to get it to work, since user-select is not
  // inherited
  const elements = document.body.getElementsByTagName("*");
  if (elements.length) {
    for (const elem of elements) {
      addStyle(elem, "user-select", "text", true);
    }
  }

  // enables text selection
  stopPropagationOfType("selectstart");
  // Enables dragging on all elements that have it
  const draggableDisabled = document.querySelectorAll("[draggable]");
  for (const elem of draggableDisabled) {
    elem.setAttribute("draggable", "auto");
  }
}

/**
 * Adds the given CSS property and value to a DOM element.
 * @param {HTMLElement} element - element we are adding the CSS property to
 * @param {string} property - CSS property name, accepts hyphenated form (i.e. user-select rather than userSelect)
 * @param {string} value - CSS property value
 * @param {boolean} important - whether to add !important
 */
function addStyle(element, property, value, important) {
  //remove previously defined property
  if (element.style.setProperty) {
    element.style.setProperty(property, "");
  } else {
    element.style.setAttribute(property, "");
  }

  //insert the new style with all the old rules
  element.setAttribute(
    "style",
    element.style.cssText +
      property +
      ":" +
      value +
      (important ? " !important" : "") +
      ";"
  );
}

The entirety of the package is open source, and can be found on GitHub.

JonLuca

JonLuca at 01:25

To get notified when I publish a new essay, please subscribe here.