Repository: mozilla/pdf.js
File: web/text_layer_builder.js (JavaScript)

Code before:
        // .endOfContent to right after (or before, depending on which side
        // of the selection the user is moving), we limit the selection jump
        // to at most cover the enteirety of the <span> where the selection
        // is being modified.
        const range = selection.getRangeAt(0);
        const modifyStart =
          prevRange &&
          (range.compareBoundaryPoints(Range.END_TO_END, prevRange) === 0 ||
            range.compareBoundaryPoints(Range.START_TO_END, prevRange) === 0);
        let anchor = modifyStart ? range.startContainer : range.endContainer;
        if (anchor.nodeType === Node.TEXT_NODE) {
          anchor = anchor.parentNode;
        }
        if (!modifyStart && range.endOffset === 0) {
          do {
            while (!anchor.previousSibling) {
              anchor = anchor.parentNode;
            }
            anchor = anchor.previousSibling;
          } while (!anchor.childNodes.length);
        }

        const parentTextLayer = anchor.parentElement?.closest(".textLayer");
        const endDiv = this.#textLayers.get(parentTextLayer);
        const anchorHighlighted = anchor.classList?.contains("highlight");
        if (endDiv && !anchorHighlighted) {
          endDiv.style.width = parentTextLayer.style.width;
          endDiv.style.height = parentTextLayer.style.height;
          anchor.parentElement.insertBefore(
            endDiv,
            modifyStart ? anchor : anchor.nextSibling
          );
        }

        prevRange = range.cloneRange();
      },
      { signal }
    );
  }
}

export { TextLayerBuilder };


Review comment (suggestion):
I think the fix would be more robust if we moved the `highlight` check to where we find the anchor:
```diff
        let anchor = modifyStart ? range.startContainer : range.endContainer;
        if (anchor.nodeType === Node.TEXT_NODE) {
          anchor = anchor.parentNode;
        }
        if (anchor.classList?.contains("highlighted")) {
          anchor = anchor.parentNode;
        }
```

However, I could not find a way to "break" selection with the current approach (tested with `test/pdfs/chrome-text-selection-markedContent.pdf`), so it's probably fine too.

Code after:
        // .endOfContent to right after (or before, depending on which side
        // of the selection the user is moving), we limit the selection jump
        // to at most cover the enteirety of the <span> where the selection
        // is being modified.
        const range = selection.getRangeAt(0);
        const modifyStart =
          prevRange &&
          (range.compareBoundaryPoints(Range.END_TO_END, prevRange) === 0 ||
            range.compareBoundaryPoints(Range.START_TO_END, prevRange) === 0);
        let anchor = modifyStart ? range.startContainer : range.endContainer;
        if (anchor.nodeType === Node.TEXT_NODE) {
          anchor = anchor.parentNode;
        }
        if (anchor.classList?.contains("highlight")) {
          anchor = anchor.parentNode;
        }
        if (!modifyStart && range.endOffset === 0) {
          do {
            while (!anchor.previousSibling) {
              anchor = anchor.parentNode;
            }
            anchor = anchor.previousSibling;
          } while (!anchor.childNodes.length);
        }

        const parentTextLayer = anchor.parentElement?.closest(".textLayer");
        const endDiv = this.#textLayers.get(parentTextLayer);
        if (endDiv) {
          endDiv.style.width = parentTextLayer.style.width;
          endDiv.style.height = parentTextLayer.style.height;
          endDiv.style.userSelect = "text";
          anchor.parentElement.insertBefore(
            endDiv,
            modifyStart ? anchor : anchor.nextSibling
          );
        }

        prevRange = range.cloneRange();
      },
      { signal }
    );
  }
}

export { TextLayerBuilder };