Fix a Safari crash in the selectionchange handler - #21778
Open
taneli-linear wants to merge 1 commit into
Open
Conversation
Safari can report a selection boundary whose backwards walk leaves the document (range end on <html>/<head> with endOffset === 0, or a boundary in a disconnected subtree). The walk then reads previousSibling of a null parentNode and throws TypeError: null is not an object (evaluating 'anchor.previousSibling') in the selectionchange listener. Guard the three reads of anchor; when the walk escapes the document, parentTextLayer is undefined and the endOfContent handling is skipped, which is correct because the selection end is not in a text layer.
taneli-linear
marked this pull request as ready for review
August 14, 2026 13:02
nicolo-ribaudo
left a comment
Collaborator
There was a problem hiding this comment.
When does anchor become null? Is there a node with no .parentNode?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Safari throws an unhandled
TypeErrorin the globalselectionchangelistener ofTextLayerBuilder:The listener searches for the node that comes before the end of the selection
(
web/text_layer_builder.js, in#enableGlobalSelectionListener).The search moves the
anchorvariable up withparentNodeand back withpreviousSibling.The search has no null check.
Only Safari comes to this code:
isFirefoxcheck.The crash is frequent. The error telemetry of our application shows more than 1500 events
from 143 users in four months, across Safari 16 through Safari 26. All events come from
Safari on macOS. Our session data shows a common sequence: the user closes a modal PDF
preview, continues to use the page, and the next
selectionchangeevent throws.The solution
Add a null guard to the three expressions that read
anchor:if (!modifyStart && range.endOffset === 0) { do { - while (!anchor.previousSibling) { + while (anchor && !anchor.previousSibling) { anchor = anchor.parentNode; } - anchor = anchor.previousSibling; - } while (!anchor.childNodes.length); + anchor = anchor?.previousSibling; + } while (anchor && !anchor.childNodes.length); } - const parentTextLayer = anchor.parentElement?.closest(".textLayer"); + const parentTextLayer = anchor?.parentElement?.closest(".textLayer");Tests
To see the crash, make the boundary shape directly. In Safari on macOS:
Without this change, the console shows the
TypeError. This reproduces on the live demoviewer (pdf.js 6.3.175 at the time of this report) in Safari.
With this change, no error occurs.
Note: the exact user gesture that makes Safari report this boundary shape is version
dependent. Our telemetry shows the events on Safari 16 through Safari 26 in normal use,
but recent Safari versions report the shape less often. The code above makes the same
boundary shape that Safari makes, and gives a stable reproduction on all versions.