Skip to content

fix: styles and scripts not injected into epub navigator#749

Open
m-abs wants to merge 15 commits intoreadium:developfrom
m-abs:fix/718
Open

fix: styles and scripts not injected into epub navigator#749
m-abs wants to merge 15 commits intoreadium:developfrom
m-abs:fix/718

Conversation

@m-abs
Copy link

@m-abs m-abs commented Feb 2, 2026

Streaming epub profile didn't inject styles and scripts into the XHTML file because the base url isn't https://readium/

This fixes the issue I had with #734, I'm not sure if it was the same problem with #718.

@qnga
Copy link
Member

qnga commented Feb 2, 2026

As usual, I've changed my mind on the way, this might roughly be the way to go. I'd like to clarify the concepts we're working with and check them against both the expectations and the implementation, but this is a headache. I'll get back to it later.

@qnga
Copy link
Member

qnga commented Feb 4, 2026

I think I've got my ideas clear.

  • We must use plain URLs in the server for Webviews to be able to resolve relative URLs.
  • On Android, if a resource has an absolute URL as href, we can use it as it is. If it's relative, we prefix it with "https://readium_package". The current approach ("http://readium/publication") seems wrong to me because absolute paths are resolved against "readium", not "readium/publication". When interception is not available, we should replace every absolute URL with a local one to be able to do injection. In theory, we should use one server/port per host used in the publication. In practice, the current approach might work well enough.
  • Historically, we've been intercepting for injection only requests for resources with relative URLs as hrefs because absolute URLs were assumed to be used with Webpubs.
  • Now we should do injection into resources if the publication conforms to the Epub profile and not do it if it doesn't. The decision should be unrelated to the URL form.
  • We can serve resources in Webpubs from the publication container or not intercept the requests, depending if we want to support transforming content in any way.

Do you share my analysis @mickael-menu?

@mickael-menu
Copy link
Member

@qnga I think your approach is sound and seems like the way to go. I like the idea of separating the host for the publication container from the one for the Readium static assets.

@qnga
Copy link
Member

qnga commented Feb 10, 2026

@m-abs You know what to do if you want to proceed?

  • One host for packaged resources. I'd say readium_package.
  • One host for assets. I'd say readium_assets.
  • Original hosts for absolute hrefs.
  • For the sake of consistency, I think relative hrefs should be resolved against base URLs before being served in non-packaged Webpubs.
  • Injection if the publication conforms to the EPUB profile. No interception if it does not (at least for now).

I reckon the minimum would be to solve the issue I pointed above and prevent injection in Webpubs.

@m-abs
Copy link
Author

m-abs commented Feb 11, 2026

@m-abs You know what to do if you want to proceed?

  • One host for packaged resources. I'd say readium_package.
  • One host for assets. I'd say readium_assets.
  • Original hosts for absolute hrefs.
  • For the sake of consistency, I think relative hrefs should be resolved against base URLs before being served in non-packaged Webpubs.
  • Injection if the publication conforms to the EPUB profile. No interception if it does not (at least for now).

I reckon the minimum would be to solve the issue I pointed above and prevent injection in Webpubs.

I'm not entirely sure how to do all that, but I'm willing to try as this is very important for us at Nota.

@qnga
Copy link
Member

qnga commented Feb 11, 2026

Actually I was too quick regarding relative URLs in non-packaged publications. We can resolve them to base URLs regardless of the publication profile. That's why I suggested the readium_package host. Packaged resources are the only resources we don't have a natural absolute URL for.

Copilot AI review requested due to automatic review settings February 23, 2026 15:26
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an issue where styles and scripts were not being injected into the EPUB navigator for streaming publications (EPUBs served over HTTP). The problem was that the WebViewServer was only checking for the hardcoded https://readium/ hostname, which didn't match streaming EPUBs that use different base URLs.

Changes:

  • Added host property to AbsoluteUrl for extracting hostname from URLs
  • Updated WebViewServer to use hostname-based routing with distinct hostnames for publications (readium_package) and assets (readium_assets)
  • Added fallback logic to handle streaming publications with arbitrary hostnames by resolving URLs against the publication's base URL

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
readium/shared/src/main/java/org/readium/r2/shared/util/Url.kt Added host property to AbsoluteUrl class to enable hostname extraction
readium/shared/src/main/java/org/readium/r2/shared/publication/Publication.kt Added imports for URL conversion utilities (unused)
readium/navigator/src/main/java/org/readium/r2/navigator/epub/WebViewServer.kt Refactored request handling to support hostname-based routing and added fallback for streaming publications

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +30 to +31
import org.readium.r2.shared.util.toUri
import org.readium.r2.shared.util.toUrl
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These imports are unused in this file. The toUrl and toUri functions are not referenced anywhere in Publication.kt.

Suggested change
import org.readium.r2.shared.util.toUri
import org.readium.r2.shared.util.toUrl

Copilot uses AI. Check for mistakes.
?: return null

servePublicationResource(
return servePublicationResource(
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant return statement. The when expression already returns the result, so the explicit return on line 74 is unnecessary. This should just be the value expression without the return keyword.

Suggested change
return servePublicationResource(
servePublicationResource(

Copilot uses AI. Check for mistakes.
else -> {
// Request is for streaming a resource, if the baseUrl an AbsoluteUrl and hostname
// is not ASSETS_HOSTNAME or READIUM_PACKAGE_HOSTNAME
val baseUrl = publication.baseUrl as? AbsoluteUrl ?: return null
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@qnga I'm unsure, if I should return null here and let the platform handle the request or if it would be better to return an errorResource() or still call servePublicationResource(...)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a baseUrl it must be absolute (I wonder why it's not in the signature, we should check). So we're dealing here with the case when there is no baseUrl. Each resource either comes from a package or has got an absolute URL as href. You've already dealt with the package case above, so I guess you should deal with absolute hrefs here.

Shortly, if the request URL is equivalent to some resource href (and the profile is EPUB), then serve the resource from the publication with injection. If it is not, serve an error resource.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a baseUrl it must be absolute (I wonder why it's not in the signature, we should check).
The baseUrl is a RelativeUrl when it is a packaged publication and an AbsoluteUrl when it is a streamed publication, which was why I added that constraint.

But I think I need to remove it again, because a packaged publication might still use an external resource. Unless we want to prevent that.
I think in our (Nota's) case, this would only happen with media-overlay/guided-navigation books which isn't affected by this.

If it is not, serve an error resource.
I'm not so sure about this.

Could there be a case where the resource url is not listed in any of the links in the publication? For instance if an image is used in the HTML but not listed in resources.

About the profile being EPUB , currently the epub/WebViewServer doesn't check for it but I'll add it.

Copy link
Member

@qnga qnga Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A packaged publication should have no baseUrl because it should have no self link. I checked with Mickaël, you can do an optional cast in Publication.baseUrl to get an absolute URL or nothing. That's already like this in the Swift toolkit.
Regarding external resources, I believe they should be served from the publication, because they are part of it. An HTTP client should probably be added to the container chain when it is built if some resources are remote.

Could there be a case where the resource url is not listed in any of the links in the publication?

In theory that's impossible, it's the point of having a manifest listing the resources. In practice, if you use publication.get you'll get a resource if the href is matching something in the package (not the manifest) and null if it's not.

Copy link
Member

@qnga qnga Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, if you use publication.get you'll get a resource if the href is matching something in the package (not the manifest) and null if it's not.

We might need to double check this in depth but if it is not the case I think that's the soundest approach anyway. I remember me doing something similar in a different case.

Copy link
Author

@m-abs m-abs Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got baseUrl that were a RelativeUrl in my packaged, but might be because our converter always adds a self-link to our RWPM.

According to this a self-link with an absolute URL is required:
https://readium.org/webpub-manifest/#23-links:~:text=Test%20Publication%22%0A%7D-,2.3.%20Links,is%20an%20absolute%20URI%20to%20the%20canonical%20location%20of%20the%20manifest.,-Example%203%3A%20Link

If it can be left our in packaged publications, I think the documentation should be updated :) and we (Nota) should fix our self link to be absolute for streaming.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See readium/webpub-manifest#119
We decided this a while back but the spec often lags behind.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed this back to checking for baseUrl being an AbsoluteUrl.

Is there anything more I need to do? We would really like to be able to stream publications :)

@m-abs
Copy link
Author

m-abs commented Feb 24, 2026

There is another WebViewServer here https://github.com/readium/kotlin-toolkit/blob/develop/readium/navigators/web/internals/src/main/kotlin/org/readium/navigator/web/internals/server/WebViewServer.kt that looks nearly identical to the original WebViewServer I changed here.

I'm not sure if that one can be used for streaming too, I don't see how to open one via the demos.navigators app.
Should I make similar changes to that one? (Assuming you agree with the changes I made here)

@qnga
Copy link
Member

qnga commented Feb 25, 2026

Yes, we should change the server in the new navigator too. I can replicate your changes by myself though, if it makes your work easier.

@m-abs
Copy link
Author

m-abs commented Feb 25, 2026

Yes, we should change the server in the new navigator too. I can replicate your changes by myself though, if it makes your work easier.

I don't mind making the changes there too, when we have agreed on the changes in the epub/WebViewServer.

Copy link
Member

@qnga qnga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides my comments, I don't think you've added a HttpClient to EPUB publications to fetch remote resources. That would prevent regressions.


val href =
// Look up the link in the publication to make sure we have the right resource.
publicationLinkFromHref(baseUrl.resolve(requestUrl))?.href?.resolve()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you resolve requestUrl? I don't think it can be relative.

Comment on lines +101 to +107
val baseUrl = publication.baseUrl as? AbsoluteUrl ?: run {
val error = ReadError.Decoding(
"baseUrl is not an AbsoluteUrl, cannot load remote resource from $requestUrl"
)
onResourceLoadFailed(requestUrl, error)
return serveErrorResponse()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need baseUrl if href is absolute. It could be safer to keep going in that case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants