Skip to content
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

Preserve page state while promoting Frame-to-Visit #448

Merged
merged 1 commit into from
Nov 19, 2021

Conversation

seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented Nov 16, 2021

The problem

The changes made in 444 removed the willRender: Visit option in
favor of allowing Frame-to-Visit navigations to participate in the
entire Visit Rendering, Snapshot Caching, and History navigating
pipeline.

The way that the willRender: guard clause was removed caused new
issues in how Frame-to-Visit navigations were treated. Removing the
outer conditional without replacing it with matching checks elsewhere
has caused Frame-to-Visit navigations to re-render the entire page,
and losing the current contextual state like scroll, focus or anything
else that exists outside the <turbo-frame> element.

Similarly, the nature of the
FrameController.proposeVisitIfNavigatedWithAction() helper resulted in
an out-of-order dispatching of turbo: and turbo:frame- events, and
resulted in turbo:before-visit and turbo:visit events firing before
turbo:frame-render and turbo:frame-load events.

The solution

To resolve the rendering issues, this commit re-introduces the
willRender: option (originally introduced in 398 and removed in
444). The option is captured in the Visit constructor and passed
along the constructed PageRenderer. This commit adds the willRender:
property to the PageRenderer class, which defaults to true unless
specified as an argument. During PageRenderer.render() calls, the
replaceBody() call is only made if willRender == true.

To integrate with caching, this commit invokes the
VisitDelegate.visitCachedSnapshot() callback with the Snapshot
instance that is written to the PageView.snapshotCache so that the
FrameController can manage the before- and after-navigation HTML to
enable integration with navigating back and forward through the
browser's history.

To re-order the events, this commit replaces the
frame.addEventListener("turbo:frame-render") attachment with a one-off
fetchResponseLoaded(FetchResponse) callback that is assigned and reset
during the frame navigation. When present, that callback is invoked
after the turbo:load event fires, which results in a much more
expected event order: turbo:before-fetch-request,
turbo:before-fetch-response, and turbo:frame- events fire first,
then the rest of the Visit's events fire.

The fetchResponseLoaded(FetchResponse) callback is an improvement, but
is still an awkward way to coordinate between the
formSubmissionIntercepted() and linkClickIntercepted() delegate
methods, the FrameController instance, and the Session instance.
It's functional for now, and we'll likely have a change to improve it
with work like what's proposed in 430 (which we can take on while
developing 7.2.0).

To ensure this behavior, this commit adds several new types of tests,
including coverage to make sure that the frame navigations can be
transformed into page Visits without lasting consequences to the
<turbo-frame> element. Similarly, another test ensures the
preservation of scroll state and input text state after a Frame-to-Visit
navigation.

There is one quirk worth highlighting: the FrameTests seem incapable
of using Selenium to serialize the { detail: { newBody: <body> } }
value out of the driven Browser's environment and into the Test harness
environment. The event itself fires, but references a detached element
or instance that results in a Stale Element Reference. To work
around that issue while delivering the bug fixes, this commit alters the
frame.html page's <html> to opt-out of serializing those events'
event.detail object (handled in
src/tests/fixtures/test.js). All other
tests that assert about turbo: events (with this.nextEventNamed or
this.nextEventOnTarget) will continue to behave as normal, the
FrameTests is the sole exception.

The problem
---

The changes made in [444][] removed the `willRender:` Visit option in
favor of allowing Frame-to-Visit navigations to participate in the
entire Visit Rendering, Snapshot Caching, and History navigating
pipeline.

The way that the `willRender:` guard clause was removed caused new
issues in how Frame-to-Visit navigations were treated. Removing the
outer conditional without replacing it with matching checks elsewhere
has caused Frame-to-Visit navigations to re-render the entire page,
and losing the current contextual state like scroll, focus or anything
else that exists outside the `<turbo-frame>` element.

Similarly, the nature of the
`FrameController.proposeVisitIfNavigatedWithAction()` helper resulted in
an out-of-order dispatching of `turbo:` and `turbo:frame-` events, and
resulted in `turbo:before-visit and `turbo:visit` events firing before
`turbo:frame-render` and `turbo:frame-load` events.

The solution
---

To resolve the rendering issues, this commit re-introduces the
`willRender:` option (originally introduced in [398][] and removed in
[444][]). The option is captured in the `Visit` constructor and passed
along the constructed `PageRenderer`. This commit adds the `willRender:`
property to the `PageRenderer` class, which defaults to `true` unless
specified as an argument. During `PageRenderer.render()` calls, the
`replaceBody()` call is only made if `willRender == true`.

To integrate with caching, this commit invokes the
`VisitDelegate.visitCachedSnapshot()` callback with the `Snapshot`
instance that is written to the `PageView.snapshotCache` so that the
`FrameController` can manage the before- and after-navigation HTML to
enable integration with navigating back and forward through the
browser's history.

To re-order the events, this commit replaces the
`frame.addEventListener("turbo:frame-render")` attachment with a one-off
`fetchResponseLoaded(FetchResponse)` callback that is assigned and reset
during the frame navigation. When present, that callback is invoked
_after_ the `turbo:load` event fires, which results in a much more
expected event order: `turbo:before-fetch-request`,
`turbo:before-fetch-response`, and `turbo:frame-` events fire first,
then the rest of the Visit's events fire.

The `fetchResponseLoaded(FetchResponse)` callback is an improvement, but
is still an awkward way to coordinate between the
`formSubmissionIntercepted()` and `linkClickIntercepted()` delegate
methods, the `FrameController` instance, and the `Session` instance.
It's functional for now, and we'll likely have a change to improve it
with work like what's proposed in [430][] (which we can take on while
developing `7.2.0`).

To ensure this behavior, this commit adds several new types of tests,
including coverage to make sure that the frame navigations can be
transformed into page Visits without lasting consequences to the
`<turbo-frame>` element. Similarly, another test ensures the
preservation of scroll state and input text state after a Frame-to-Visit
navigation.

There is one quirk worth highlighting: the `FrameTests` seem incapable
of using Selenium to serialize the `{ detail: { newBody: <body> } }`
value out of the driven Browser's environment and into the Test harness
environment. The event itself fires, but references a detached element
or instance that results in a [Stale Element Reference][]. To work
around that issue while delivering the bug fixes, this commit alters the
`frame.html` page's `<html>` to opt-out of serializing those events'
`event.detail` object (handled in
[src/tests/fixtures/test.js](./src/tests/fixtures/test.js)). All other
tests that assert about `turbo:` events (with `this.nextEventNamed` or
`this.nextEventOnTarget`) will continue to behave as normal, the
`FrameTests` is the sole exception.

[398]: hotwired#398
[430]: hotwired#430
[441]: hotwired#441
[444]: hotwired#444
[Stale Element Reference]: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Errors/StaleElementReference
@seanpdoyle
Copy link
Contributor Author

@dhh if this is a good enough fix, I think that it'd be good to include in 7.1.0-rc2.

@dhh dhh merged commit 539b249 into hotwired:main Nov 19, 2021
@seanpdoyle seanpdoyle deleted the frame-to-page-visit-state branch November 19, 2021 14:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants