-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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 scrolling on execution and switching notebook mode #15702
Fix scrolling on execution and switching notebook mode #15702
Conversation
Thanks for making a pull request to jupyterlab! |
packages/notebook/src/actions.tsx
Outdated
// If a cell is outside of viewport and scrolling is needed, | ||
// prefer to align the cell to the top viewport edge, | ||
// rather than to the bottom (so that editor is visible) | ||
void Private.handleRunState(notebook, state, true, 'start'); |
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.
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.
It seems to me that we would want to restore the JupyterLab 3 behaviour here, maybe a variation of it (say "top edge in the 1/4 of the viewport from the top").
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.
So the problem with the earlier implementation (before #15288) was not that auto
alignment differed from Lab 3.6, just that "center" was implemented as "align the middles of the cell and the viewport". This obviously led to very annoying behaviour for cells longer than viewport as seemingly random part of the output was shown:
I will push a commit to fix that behaviour, which should simplify the code too.
I now see that the fault of cell scrolling on switching to command mode affects many more actions than just running cells; for example pressing Esc in edit mode for a cell partially out of view would scroll, opening context menu over such a cell would scroll, etc. I will try to switch scroll on focus behaviour off by default. |
6b37a7c
to
133a55c
Compare
if (this.viewModel.windowingActive) { | ||
if (!this._itemsResizeObserver) { | ||
this._itemsResizeObserver = new ResizeObserver( | ||
this._onItemResize.bind(this) |
Check warning
Code scanning / CodeQL
Superfluous trailing arguments
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.
False positive from testing code, resolved in 0870ffe
this._outerElement.addEventListener('scroll', this, passiveIfSupported); | ||
|
||
this._scrollbarResizeObserver = new ResizeObserver( | ||
this._adjustDimensionsForScrollbar.bind(this) |
Check warning
Code scanning / CodeQL
Superfluous trailing arguments
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.
False positive from testing code, resolved in 0870ffe
} else { | ||
if (!this._areaResizeObserver) { | ||
this._areaResizeObserver = new ResizeObserver( | ||
this._onAreaResize.bind(this) |
Check warning
Code scanning / CodeQL
Superfluous trailing arguments
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.
False positive from testing code, resolved in 0870ffe
Note for reviewer: failures of
|
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 done reviewing, but I thought I would go ahead and leave the review comments I have so far.
I'm not sure I feel confident about my ability to review the changes to the windowed list classes.
* If the item is less than one viewport away, scroll so that it becomes fully visible (following the `auto` heuristics). | ||
* If the item is more than one viewport away, scroll so that it is centered within the viewport (`center` if smaller than viewport, `top-center` otherwise). | ||
* center - Align the middle of the item with the middle of the viewport (it only works well for items smaller than the viewport). | ||
* top-center - Align the top of the item with the middle of the viewport (works well for items larger than the viewport). |
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.
Nice
packages/notebook/src/actions.tsx
Outdated
// If a cell is outside of viewport and scrolling is needed, | ||
// prefer to align the cell to the top viewport edge, | ||
// rather than to the bottom (so that editor is visible) | ||
void Private.handleRunState(notebook, state, true, 'center'); |
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.
Why 'center' and not 'top-center'? I thought the idea was to keep the cell that we just left in view, which we cannot guarantee if we scroll the center of the next cell into the center of the viewport.
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.
Great question! This is because it only applies to auto
strategy and handleRunState
uses the smart
strategy, which will:
- (a) not scroll if significantly visible, otherwise
- (b) scroll using
auto
heuristics if less than one viewport away
this point is important because if an item is less than one viewport and not significantly visible it implies that it fits in the viewport hence we can applycenter
rather thantop-center
. - (c) choose between
top-center
andcenter
depending on item size.
So in (b) we can use center
over top-center
; but should we? For small cells it is nicer, and in a rare case of item size being equal to viewport we loose past context (future context would not be visible either way), so maybe we could choose between center
and top-center
depending on item size, but I did not want to make the logic any more complicated; in fact I was considering removing this fallback to auto
heuristic here but was also cautious not to modify the logic too much in case if downstream already depends on smart
falling back to auto
in this circumstance. In either case, yes we could also use top-center
here. What do you think?
This also highlights that the comment above the line you commented on is outdated because I had not updated it since the previous solution was in place. I will update it now.
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.
The comment was updated in 5b02962
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 help but wondering if the added code complexity outweighs the UX benefit. Seems to me that if we don't introduce the alignPreference
parameter then the consequence is that in the scenario labeled (b), in which the cell fits within the viewport and is less than one viewport away, then the cell will be scrolled to in such a way that either its top edge will align with the top edge of the scrollable container or its bottom edge with the bottom edge. I'm probably missing something but I don't see how that's significantly worse than scrolling the cell's top edge (or center) to the center of the scroll-container.
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 see that a lot of thought, care, and hard work has gone into this PR, so I hesitate to ask this question, but I was wondering if it would make sense to split up this PR. I may be misunderstanding the code, but it seems that only some of the code changes in this PR strictly address fixing the regressions that caused the bug reported in issue #15693. The other code changes seem like enhancements. Would it make sense to separate the two?
Marking request changes for my question about the test code.
packages/notebook/src/actions.tsx
Outdated
// If a cell is outside of viewport and scrolling is needed, | ||
// prefer to align the cell to the top viewport edge, | ||
// rather than to the bottom (so that editor is visible) | ||
void Private.handleRunState(notebook, state, true, 'center'); |
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 help but wondering if the added code complexity outweighs the UX benefit. Seems to me that if we don't introduce the alignPreference
parameter then the consequence is that in the scenario labeled (b), in which the cell fits within the viewport and is less than one viewport away, then the cell will be scrolled to in such a way that either its top edge will align with the top edge of the scrollable container or its bottom edge with the bottom edge. I'm probably missing something but I don't see how that's significantly worse than scrolling the cell's top edge (or center) to the center of the scroll-container.
No, this is incorrect. As soon as we change
I do not see any changes in this PR which are not directly addressing the erractic jumps reported in #15693 (and while demonstrated on one example - ctrl + enter, also appearing in scenario of shift + enter as described in the issue in detail in course of investigation; further, given the above resolution approach there is not even a way to fix ctrl + enter without fixing shift + enter). |
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.
Okay thanks for the clarification.
Apart from some reservations I have about increasing the complexity of the code with the alignPreference
parameter, this code looks good to me!
However, I still think there's something amiss with those test files. Could you diff them to make sure?
@@ -131,15 +124,53 @@ test.describe('Notebook scroll on execution', () => { | |||
// The third cell should be positioned at the bottom, revealing between 10 to 20% of its content. | |||
await expect(thirdCellLocator).toBeInViewport({ ratio: 0.1 }); | |||
await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); | |||
await expect(thirdCellLocator).not.toBeInViewport({ ratio: 0.2 }); |
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 line is duplicated
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.
Great catch, thanks! It now looks good for me:
20c20
< windowingMode: 'full'
---
> windowingMode: 'none'
25c25
< test.describe('Notebook scroll on navigation (with windowing)', () => {
---
> test.describe('Notebook scroll on navigation (no windowing)', () => {
65c65
< test.describe('Notebook scroll on execution (with windowing)', () => {
---
> test.describe('Notebook scroll on execution (no windowing)', () => {
172c172
< test.describe('Notebook scroll over long outputs (with windowing)', () => {
---
> test.describe('Notebook scroll over long outputs (no windowing)', () => {
@gabalafou does it look good to you now? |
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.
Okay, I'm done reviewing and I approve with one reservation, which is that I'm still not sure whether the UX benefit of alignPreference
is worth the added complexity (not sure if you saw my comment asking about that). But this is not blocking for me. I also left some comments in this last round of review, but these are also not blocking.
Great job unifying the windowing and no-windowing UX!
* Handle viewport area resize. | ||
*/ | ||
private _onAreaResize(_entries: ResizeObserverEntry[]): void { | ||
this._scrollBackToItemOnResize(); |
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.
Just checking my own understanding here.
Is this line more for code symmetry and to cover edge cases than to cover a commonly occurring scenario? I mean, it's generally unlikely that an area resize will happen within 100 ms of the app scrolling to some item, right?
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.
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.
Ah, for some reason I thought the area resize observer was observing the outer (scrollable) element (for e.g. window resize). Thanks for the clarification.
@@ -610,7 +610,8 @@ export class NotebookToCFactory extends TableOfContentsFactory<NotebookPanel> { | |||
console.debug('scrolling to heading: using fallback strategy'); | |||
await widget.content.scrollToItem( | |||
widget.content.activeCellIndex, | |||
this.scrollToTop ? 'start' : undefined | |||
this.scrollToTop ? 'start' : undefined, | |||
0 |
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.
Hmm, I never asked about this change (margin=0) and I'm not sure I understand why it's here
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.
Good question. First, this is the fallback strategy which is unlikely to be used. Before this PR it was broken in defer mode because of wrong node being used for scrolling further down the line. Currently both defer and full mode will work, invoking a logic which considers the margin. Now, the default visibility margin for scrolling on action is 25%. But when user uses ToC they often want to find the heading and if it was partially revealed and the clicked either way it means that they expect actions anyways (hence it should be zero).
} | ||
} | ||
|
||
window.ResizeObserver = ResizeObserverMock; |
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.
Also checking my understanding here. If the ResizeObserver is basically just a no-op mock, that means that none of the tests are hitting code paths that would lead to _scrollBackToItemOnResize() being called, correct?
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.
that means that none of the tests are hitting code paths
More precisely none of the unit tests get to call _scrollBackToItemOnResize()
, but the integration tests do. In fact if one was to remove it the visual tests would fail.
Co-authored-by: gabalafou <gabriel@fouasnon.com>
for more information, see https://pre-commit.ci
Thank you for the review @gabalafou!
I just want to acknowledge that I saw your comment and take responsibility of dealing with the added complexity arising from having this parameter if it ever becomes a problem (and I believe this is worth the - small - UX benefit). |
References
Fixes #15693, solving two regressions introduced in 4.1 cycle:
Code changes
_outerElement
)focus
triggering a browser scroll - regression introduced by Fix tab trap notebook cells #14115 (seepreventScrollOnFocus
)precomputed
argument).The last point might be better solved by changing when scroll gets called when a cell is run. Currently the sequence is:
This does not work great when executing a cell which has long output in the following scenario:
Therefore, it might be tempting to change this sequence to:
But this would lead to surprising results if execution takes longer than ~100ms because a user could manually scroll away and be surprised that once the cell(s) completed execution, they are snapped back to the cell after the last executed cell.
Thus we would also need to cancel the pending scroll to next cell if user has already scrolled the view.
User-facing changes
First, spurious scrolling is eliminated if the following cell (for shift + enter) or current cell (for ctrl + enter) is significantly visible (see examples below)
Shift + Enter: short cell → long cell
defer
mode)defer
mode)Note: this already worked in
full
mode since 4.0.9+ thanks to #15288 (see details).full
mode)full
mode)full
mode)Ctrl + Enter: long cell (partially out of view), Chrome
defer
mode)defer
mode)Note: this was also broken in
full
, and this PR also fixes it in the full mode (see details).full
mode)full
mode)Second, when execution with Shift + Enter invokes scrolling to next cell which was out of viewport, that cell will be positioned so that its top is in the middle of the viewport.
Backwards-incompatible changes
None