Prevent highlighting on photos

Prevent highlighting on manga sites like WeebCentral

// ==UserScript==
// @name         Prevent highlighting on photos
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description  Prevent highlighting on manga sites like WeebCentral
// @author       You
// @match        https://weebcentral.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=create.t3.gg
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

(() => {
  let enabled = true;

  const STYLE_ID = "tm-no-select-style";

  // CSS that blocks selection everywhere except form/editable elements
  const css = `
    html, body, * {
      -webkit-user-select: none !important;
      -moz-user-select: none !important;
      -ms-user-select: none !important;
      user-select: none !important;
    }
    input, textarea, select, [contenteditable=""], [contenteditable="true"] {
      -webkit-user-select: text !important;
      -moz-user-select: text !important;
      -ms-user-select: text !important;
      user-select: text !important;
    }
  `;

  const eventsToBlock = [
    "selectstart", // text selection start
    "dragstart", // double-click + drag
    "mousedown", // some sites use this to trigger selection
    "pointerdown", // pointer-based selection
    "dblclick", // double-click selection
  ];

  // Add a style element to a (shadow) root
  function injectStyleInto(root) {
    if (!root || !root.ownerDocument) return;
    const doc = root.ownerDocument;
    // Avoid duplicates per root
    const existing = [
      ...(root.querySelectorAll ? root.querySelectorAll(`#${STYLE_ID}`) : []),
    ].length;
    if (existing) return;

    const style = doc.createElement("style");
    style.id = STYLE_ID;
    style.textContent = css;
    (root.head || root).appendChild(style);
  }

  // Attach event blockers at the root level
  function attachEventBlockers(target) {
    eventsToBlock.forEach((evt) => {
      target.addEventListener(evt, onBlocker, {
        capture: true,
        passive: false,
      });
    });
  }

  // Permit selection inside form/editable controls
  function isEditable(el) {
    if (!el) return false;
    if (el.closest("input, textarea, select")) return true;
    const ce = el.closest("[contenteditable]");
    return (
      ce &&
      (ce.getAttribute("contenteditable") === "" ||
        ce.getAttribute("contenteditable") === "true")
    );
  }

  function onBlocker(e) {
    if (!enabled) return;
    if (isEditable(e.target)) return;
    e.stopPropagation();
    e.preventDefault();
  }

  // Process the main document and any existing shadow roots
  function processDocument(doc = document) {
    injectStyleInto(doc);
    attachEventBlockers(doc);

    // Walk the tree to find shadow roots already attached
    const walker = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT);
    let node;
    while ((node = walker.nextNode())) {
      if (node.shadowRoot) {
        processShadowRoot(node.shadowRoot);
      }
    }
  }

  // Handle a shadow root
  function processShadowRoot(sr) {
    injectStyleInto(sr);
    attachEventBlockers(sr);
  }

  // Intercept future shadow roots
  const origAttachShadow = Element.prototype.attachShadow;
  Element.prototype.attachShadow = function (init) {
    const sr = origAttachShadow.call(this, init);
    // Process after creation
    queueMicrotask(() => processShadowRoot(sr));
    return sr;
  };

  // Observe added elements to catch closed-over cases where styles might be removed
  const mo = new MutationObserver((muts) => {
    if (!enabled) return;
    for (const m of muts) {
      if (m.type === "childList") {
        m.addedNodes.forEach((n) => {
          // Re-inject style into document if removed
          if (n.nodeType === 1) {
            const el = /** @type {Element} */ (n);
            // If a <style> got removed elsewhere, ensure one exists
            if (!document.getElementById(STYLE_ID)) injectStyleInto(document);
            // Shadow roots on newly added custom elements (if any) get handled by attachShadow hook
          }
        });
      }
    }
  });

  function enable() {
    enabled = true;
    injectStyleInto(document);
    attachEventBlockers(document);
  }

  function disable() {
    enabled = false;
  }

  // Keyboard toggle: Ctrl + Alt + U
  window.addEventListener(
    "keydown",
    (e) => {
      if (e.ctrlKey && e.altKey && (e.key === "u" || e.key === "U")) {
        enabled ? disable() : enable();
        // Optional toast
        try {
          const msg = `No-select: ${enabled ? "ON" : "OFF"}`;
          console.log(msg);
        } catch {}
        e.preventDefault();
        e.stopPropagation();
      }
    },
    { capture: true }
  );

  // Initialize
  if (document.readyState === "loading") {
    // document-start, but DOM not ready yet
    processDocument(document);
  } else {
    processDocument(document);
  }
  mo.observe(document.documentElement || document, {
    childList: true,
    subtree: true,
  });
})();