Speak from Here fails for PDF/fixed-layout selections with WrongDocumentError
Environment
- Foliate: 3.3.0-r1 downstream package
- Upstream branch checked:
johnfactotum/foliate gtk4
foliate-js checked: johnfactotum/foliate-js main
- Document type where observed: PDF / fixed-layout renderer
Steps to reproduce
- Open a PDF or other fixed-layout document.
- Select text on a rendered page.
- Choose
Speak from Here from the selection popover.
Expected behavior
Foliate should build the SSML for the selected range and emit the selection
message to the app-side TTS path.
Actual behavior
Foliate throws before the selection message is emitted:
foliate:///foliate-js/tts.js:262:40: CONSOLE JS ERROR Unhandled Promise Rejection: WrongDocumentError: The object is in the wrong document.
In my setup, the downstream TTS backend never receives anything: there is no
Speech Dispatcher SPEAK request and no HTTP request to the custom TTS server.
That matches the exception happening before ssml is produced.
Analysis
The speak-from-here handler captures a valid selection range, then calls
this.view.initTTS() and constructs ssml with this.view.tts.from(range):
https://github.com/johnfactotum/foliate/blob/gtk4/src/reader/reader.js#L425-L428
foliate-js currently initializes TTS from the first renderer content document:
https://github.com/johnfactotum/foliate-js/blob/main/view.js#L584-L588
For the fixed-layout renderer, getContents() returns all iframe documents:
https://github.com/johnfactotum/foliate-js/blob/main/fixed-layout.js#L308-L311
So in a spread, TTS can be initialized for iframe document A while the selected
range belongs to iframe document B. Then TTS.from(range) compares that range
against ranges generated from the TTS document:
https://github.com/johnfactotum/foliate-js/blob/main/tts.js#L259-L265
When those ranges belong to different documents, Range.compareBoundaryPoints
throws WrongDocumentError.
PDFs appear to enter this path because makePDF() creates a pre-paginated
book:
https://github.com/johnfactotum/foliate-js/blob/main/pdf.js#L129
Patch direction
A local patch that fixes the code path is:
- update
View.initTTS() to accept an optional selected Range and initialize
TTS from range.commonAncestorContainer.ownerDocument when provided;
- pass the captured selection range from the
speak-from-here handler.
I also kept a fallback that chooses the currently selected iframe document when
no range is passed, before falling back to getContents()[0].doc.
Speak from Here fails for PDF/fixed-layout selections with WrongDocumentError
Environment
johnfactotum/foliategtk4foliate-jschecked:johnfactotum/foliate-jsmainSteps to reproduce
Speak from Herefrom the selection popover.Expected behavior
Foliate should build the SSML for the selected range and emit the selection
message to the app-side TTS path.
Actual behavior
Foliate throws before the selection message is emitted:
In my setup, the downstream TTS backend never receives anything: there is no
Speech Dispatcher
SPEAKrequest and no HTTP request to the custom TTS server.That matches the exception happening before
ssmlis produced.Analysis
The
speak-from-herehandler captures a valid selectionrange, then callsthis.view.initTTS()and constructsssmlwiththis.view.tts.from(range):https://github.com/johnfactotum/foliate/blob/gtk4/src/reader/reader.js#L425-L428
foliate-jscurrently initializes TTS from the first renderer content document:https://github.com/johnfactotum/foliate-js/blob/main/view.js#L584-L588
For the fixed-layout renderer,
getContents()returns all iframe documents:https://github.com/johnfactotum/foliate-js/blob/main/fixed-layout.js#L308-L311
So in a spread, TTS can be initialized for iframe document A while the selected
range belongs to iframe document B. Then
TTS.from(range)compares that rangeagainst ranges generated from the TTS document:
https://github.com/johnfactotum/foliate-js/blob/main/tts.js#L259-L265
When those ranges belong to different documents,
Range.compareBoundaryPointsthrows
WrongDocumentError.PDFs appear to enter this path because
makePDF()creates a pre-paginatedbook:
https://github.com/johnfactotum/foliate-js/blob/main/pdf.js#L129
Patch direction
A local patch that fixes the code path is:
View.initTTS()to accept an optional selectedRangeand initializeTTS from
range.commonAncestorContainer.ownerDocumentwhen provided;speak-from-herehandler.I also kept a fallback that chooses the currently selected iframe document when
no range is passed, before falling back to
getContents()[0].doc.