-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix infinite sidebar loading in Firefox >=130 with PDFs #6541
Conversation
19e490a
to
a0fae12
Compare
@@ -94,6 +94,7 @@ class FakePDFViewerApplication { | |||
this.dispatchDOMEvents = domEvents; | |||
this.initialized = false; | |||
this.newFingerprintAPI = newFingerprintAPI; | |||
this.downloadComplete = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was previously not initialized, relying on the "falsy" behavior of undefined
.
Now we need to initialize to ensure the code branches that need to run are preserved.
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #6541 +/- ##
=======================================
Coverage 99.43% 99.43%
=======================================
Files 270 270
Lines 10213 10219 +6
Branches 2416 2417 +1
=======================================
+ Hits 10155 10161 +6
Misses 58 58 ☔ View full report in Codecov by Sentry. |
@@ -70,9 +86,10 @@ export class PDFMetadata { | |||
* @param app - The `PDFViewerApplication` global from PDF.js | |||
*/ | |||
constructor(app: PDFViewerApplication) { | |||
this._loaded = pdfViewerInitialized(app).then(() => { | |||
this._loaded = pdfViewerInitialized(app).then(async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not going to change that here, as it may have other implications, but I think that we could simply do this:
this._loaded = pdfViewerInitialized(app)
.then(() => app.pdfDocument.getDownloadInfo())
.then(() => app);
But we would probably have to drop support for some older versions of PDF.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we would probably have to drop support for some older versions of PDF.js
We need to clarify the supported range of PDF.js versions. I think there are some existing code comments somewhere. The main considerations would be:
- The version of PDF.js used in Via and the current extension
- Versions of PDF.js used in the last N Firefox releases
- Which older versions of https://github.com/hypothesis/pdf.js-hypothes.is we want to support
Of these, (3) is the main potential source of old PDF.js versions still in use. We might need to do some metrics gathering in the client to find out how many requests are coming from old releases, and also add some kind of warning banner if the client is loaded in an old version that is unsupported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know https://github.com/hypothesis/pdf.js-hypothes.is existed.
/** | ||
* @return A promise that is resolved when the document's data is loaded. | ||
* It is resolved with an {Object} that contains the `length` property | ||
* that indicates size of the PDF data in bytes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I basically copied the JSDoc from PDF.js repo https://github.com/mozilla/pdf.js/blob/a1b45d6e69d37b76e675a7f247943b70f186baca/src/display/api.js#L1024-L1028
a0fae12
to
0c92b50
Compare
} = {}, | ||
) { | ||
this.url = url; | ||
this.documentInfo = undefined; | ||
this.metadata = undefined; | ||
this.pdfDocument = null; | ||
this.pdfDocument = new FakePDFDocumentProxy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need this to be set from the beginning now, so I changed it so that it can be created "empty", but exposes a finishLoading
method that is invoked when the fake app's own finishLoading
method is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible in PDF.js that pdfDocument
is unset either temporarily or permanently when Hypothesis is loaded? Do we handle that gracefully?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to our type definitions, it is mandatory, but looking at the code, it can be null apparently https://github.com/mozilla/pdf.js/blob/f68310b7b1db39a934124acc90867a949da2d091/web/app.js#L971
Also, if you check that code, the prop is initialized only when load
method is called.
I'll create an issue to try to reproduce what happens when it is not set and handle it properly.
0c92b50
to
7dedfaf
Compare
7dedfaf
to
12830f4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't offer much insight on the code changes themselves but I followed the test instructions in firefox 130.0-2 and 129.0.2-1 and everything works as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes generally look good in the happy path case. I had a query about whether we gracefully handle cases where the PDF has failed to load, has not yet started loading (is this possible) or the viewer was loaded without specifying a PDF.
* For newer PDF.js versions we wait for | ||
* `PDFViewerApplication.pdfDocument.getDownloadInfo()` to resolve. | ||
*/ | ||
async function isPDFDownloaded(app: PDFViewerApplication): Promise<boolean> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function has type Promise<boolean>
but it only ever returns true. An API like whenPDFDownloaded(app): Promise<void>
would probably give a better cue to readers about how it behaves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function has type
Promise<boolean>
but it only ever returns true
Hmm, I'm not sure if this is actually the case.
Previously we were relying in the downloadComplete
prop, which could be true or false. Now it can also be undefined
, but it can still be false
I guess? 🤔
In that case, if the prop is set and is false, we would be falling back to the other logic that was already in place, which listens for documentloaded
/documentload
on the event bus.
Or am I missing something?
@@ -70,9 +86,10 @@ export class PDFMetadata { | |||
* @param app - The `PDFViewerApplication` global from PDF.js | |||
*/ | |||
constructor(app: PDFViewerApplication) { | |||
this._loaded = pdfViewerInitialized(app).then(() => { | |||
this._loaded = pdfViewerInitialized(app).then(async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we would probably have to drop support for some older versions of PDF.js
We need to clarify the supported range of PDF.js versions. I think there are some existing code comments somewhere. The main considerations would be:
- The version of PDF.js used in Via and the current extension
- Versions of PDF.js used in the last N Firefox releases
- Which older versions of https://github.com/hypothesis/pdf.js-hypothes.is we want to support
Of these, (3) is the main potential source of old PDF.js versions still in use. We might need to do some metrics gathering in the client to find out how many requests are coming from old releases, and also add some kind of warning banner if the client is loaded in an old version that is unsupported.
@@ -1,6 +1,7 @@ | |||
import { delay } from '@hypothesis/frontend-testing'; | |||
import EventEmitter from 'tiny-emitter'; | |||
|
|||
import { promiseWithResolvers } from '../../../shared/promise-with-resolvers'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could replace this with Promise.withResolvers
going forwards.
} = {}, | ||
) { | ||
this.url = url; | ||
this.documentInfo = undefined; | ||
this.metadata = undefined; | ||
this.pdfDocument = null; | ||
this.pdfDocument = new FakePDFDocumentProxy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible in PDF.js that pdfDocument
is unset either temporarily or permanently when Hypothesis is loaded? Do we handle that gracefully?
/** | ||
* Indicates the download of the PDF has completed. | ||
* This prop is not set in PDF.js >=4.5, in which case you should use | ||
* `PDFViewerApplication.pdfDocument.getDownloadInfo()` instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A link to the commit that removed this property upstream would be useful.
When using the bookmarklet in Firefox, we use the PDF.js version embedded on it.
Firefox 130 has released with a new version of PDF.js which removes the prop we used to use to know that the PDF was downloaded, cuasing the sidebar to stay in an infinite loading state. See the conversation in slack https://hypothes-is.slack.com/archives/C2BLQDKHA/p1725653987800389
This PR adds a fallback check to
PDFViewerApplication.pdfDocument.getDownloadInfo()
, which returns a promise when the PDF is downloaded, and is what used to be used to set the removed prop (see https://github.com/mozilla/pdf.js/pull/18463/files#diff-e24e4bd32090b0c400a70f02d1b844df95c9bbe62cd0b280f4397e9f4194fe44L1224), so it should be equivalent.I did not remove the previous check, but combined both using the new one as a fallback. This will reduce potential regressions in older versions of PDF.js, Firefox, or when using other browsers. However, it seems to be possible to rely only on
PDFViewerApplication.pdfDocument.getDownloadInfo()
, as it has been around for several versions.Testing steps
This PR requires mostly sanity checking a few scenarios, being:
javascript:(function()%7Bwindow.hypothesisConfig=function()%7Breturn%7BshowHighlights:true,appType:'bookmarklet'%7D;%7D;var d=document,s=d.createElement('script');s.setAttribute('src','http://localhost:5000/embed.js');d.body.appendChild(s)%7D)();
These three scenarios should be tested in Firefox 130, to verify it works now, but also Firefox <130 and other browsers (chrome and/or Safari) to verify there are no regressions for those.