-
Notifications
You must be signed in to change notification settings - Fork 10.2k
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
Refactor the previous history rewriting logic #6200
Conversation
Unfortunately I'm not able to verify this patch. |
@Snuffleupagus Oops. It seems that |
Actually, if you're willing to live with this issue for just a little while longer, it might actually be fixed "for free" by the Note that the current |
I think that this bug can be attributed to a browser bug. Here's a reduced steps-to-reproduce:
(sanity check:)
(exchanging EDIT: Queing the .forward call in the event loop appears to work around the issues (i.e. wrapping the second history call in |
Reported upstream: https://code.google.com/p/chromium/issues/detail?id=510026 |
Where does this leave the PR? If this is in fact a browser bug, should we attempt to work around it here or should we just file a tracking issue for the WebKit bug? |
@timvandermeij I've read the spec, and it doesn't explicitly state that the history has to immediately be updated after calling history. Actually, in the way that it's worded, it seems that it's perfectly fine that Because of this, and because this issue affects all WebKit-derived browsers, I'll edit this PR with a work-around to this issue. I'll ping you both again when the PR has been updated. Here are the relevant portions of the spec:
and
(queuing a task in the event loop means that the task does not have to be executed immediately. Think of queuing as wrapping |
Sounds good. Thank you for looking into this. I'm marking this as work in progress for now until it is updated. |
fcb7f61
to
6da37b8
Compare
@timvandermeij @Snuffleupagus Please take a look at the updated PR. |
@Rob--W I'm sorry for the delay in getting back to you, but I've been quite busy recently. Re: #6272 (comment) However, I do feel that this PR makes the code more complicated. And since it'd like to get rid of this special case anyway (in PR #6272), I'm wondering if we couldn't just remove these lines instead: https://github.com/mozilla/pdf.js/blob/master/web/pdf_history.js#L91-L104. |
No problem!
In my opinion, the existing code was complicated (it took some efforts to follow the existing logic), and the refactoring into smaller, focused and documented functions enhance the readability / maintainability of this logic.
Possibly yes, but that still needs some review and testing, which may take a while. I think that a possible concern with merging this patch is that your work at #6272 cannot immediately be merged, but that can simply be solved by adding a All together, I think that merging this patch has more advantages than disadvantages (to be precise: no advantages for Firefox, known advantages for WebKit/Blink-based browsers, no known disadvantages). If this patch doesn't get merged, I'll include the patch on top of the chrome extension branch (to fix the bug for 500K users). That act would not resolve the bug for users who put PDF.js on their website though. |
if (!self.historyUnlocked) { | ||
return; | ||
} | ||
if (evt.state) { | ||
if (self._isStateObjectDefined(evt.state)) { |
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.
Please revert this change, since there's an edge-case where it can can have undesired effects.
In the GENERIC
viewer (which support opening multiple PDF files), evt.state
may refer to a previously opened PDF file. In that case, nothing should be done; note how _goTo
returns early for this case (given self._isStateObjectDefined(evt.state) == false
).
If this is changed, we could incorrectly reach the code below, which should only happen when the user modifies the hash.
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.
Thanks, done!
Considering that this code is only relevant for in an edge case, I won't fight you on this; let's just agree to disagree :-)
I'm quite used to having to rebase around various merge conflicts, so I did not in any way consider that to be a blocker! |
When the user edits the URL and changes the reference fragment (hash), PDF.js intercepts this action, and saves the then-current history state in the previous history entry. This is implemented by navigating back, editing the history and navigating forward again. The current logic has a flaw: It assumes that calling history.back() and history.forward() immediately updates the history state. This is however not guaranteed by the web standards, which states that calling e.g. history.back "must traverse the history by a delta -1", which means that the browser must QUEUE a task to traverse the session history, per spec: http://w3.org/TR/2011/WD-html5-20110113/history.html#dom-history-back https://html.spec.whatwg.org/multipage/browsers.html#dom-history-back Firefox and Internet Explorer deviate from the standards by immediately changing the history state instead of queuing the navigation. WebKit derived browsers (Chrome, Opera, Safari) and Opera presto do not. The user-visible consequence of strictly adhering to the standards in PDF.js can be shown as follows: 1. Edit the URL. 2. Append #page=2 for example. 3. Press Enter. -> Presto and WebKit: PDF.js reverts to the previous URL. -> Gecko and Trident: PDF.js keeps the new URL, as expected. To fix the issue, modification of the previous history item happens in a few asynchronous steps, guided by the popstate event to detect when the history navigation request has been committed. -- Some more implementation notes: I have removed the preventDefault and stopPropagation calls, because popstate is not cancelable, and window is already the last target of the event propagation. The previous allowHashChange logic was hard to follow, because it did not explain that hashchange will be called twice; once during the popstate handler for history.back() (which will reset allowHashChange), and again for history.forward() (where allowHashChange will be false). The purpose of allowHashChange is now more explicit, by incorporating the logic in the replacePreviousHistoryState helper function.
6da37b8
to
cdea75d
Compare
Refactor the previous history rewriting logic
When the user edits the URL and changes the reference fragment (hash), PDF.js intercepts this action, and saves the then-current history state in the previous history entry. This is implemented by navigating back, editing the history and navigating forward again.
The current logic has a flaw: It assumes that calling
history.back()
andhistory.forward()
immediately updates the history state. This is however not guaranteed by the web standards, which states that calling e.g.history.back
"must traverse the history by a delta -1", which means that the browser must QUEUE a task to traverse the session history, per spec (http://w3.org/TR/2011/WD-html5-20110113/history.html#dom-history-back and https://html.spec.whatwg.org/multipage/browsers.html#dom-history-back).Firefox and Internet Explorer deviate from the standards by immediately changing the history state instead of queuing the navigation. WebKit derived browsers (Chrome, Opera, Safari) and Opera presto do not.
The user-visible consequence of strictly adhering to the standards in PDF.js can be shown as follows:
-> Presto and WebKit: PDF.js reverts to the previous URL.
-> Gecko and Trident: PDF.js keeps the new URL, as expected.
To fix the issue, modification of the previous history item happens in a few asynchronous steps, guided by the popstate event to detect when the history navigation request has been committed.
(I have also put the above message in the commit message together with some additional notes about the implementation).
Assigned to @Snuffleupagus for review because you introduced the logic in 766b92c