Skip to content

Speak from Here fails for PDF/fixed-layout selections with WrongDocumentError #1687

Description

@AprilGrimoire

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

  1. Open a PDF or other fixed-layout document.
  2. Select text on a rendered page.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions