Skip to content

Handle requests for new windows by proposing a new visit#225

Merged
svara merged 7 commits into
mainfrom
zoe/new-window-navigation
Jul 1, 2026
Merged

Handle requests for new windows by proposing a new visit#225
svara merged 7 commits into
mainfrom
zoe/new-window-navigation

Conversation

@zoejessica

Copy link
Copy Markdown
Contributor

Changes default Hotwire Native behaviour to automatically start a new visit to the requested url when the WKUIDelegate receives a request to create a new web view for a navigation.

The motivation here was to handle links requesting new windows that are embedded in an iframe seamlessly through HotwireNative navigation, rather than discarding them. For example, routing the links in a Youtube embed out to the native Youtube app. Previously, iOS clients would have to subclass WKUIController and implement their own handling of this WKUIDelegate method:

func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?

The new behaviour proposed here matches the implementation in hotwire-native-android in HotwireWebChromeClient.

We attempt to filter out common blank urls before calling the delegate method. The Android implementation avoids this step by using requestFocusNodeHref, which isn't available to us.

If Navigator is not used, the default implementation of the new delegate method does nothing, so this is a non-breaking change.

Changes default Hotwire Native behaviour to automatically start a new visit to the requested url when the WKUIDelegate receives a request to create a new web view for a navigation.

The motivation here was to handle links requesting new windows that are embedded in an iframe seamlessly through HotwireNative navigation, rather than discarding them. Previously iOS clients would have to subclass WKUIController and implement their own handling of the WKUIDelegate method func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView?

The new behaviour proposed here matches the implementation in hotwire-native-android in HotwireWebChromeClient https://github.com/hotwired/hotwire-native-android/blob/588d69c124f2baf7fe605c91fd96d67896e6b534/core/src/main/kotlin/dev/hotwire/core/turbo/webview/HotwireWebChromeClient.kt#L74

We attempt to filter out common blank urls before calling the delegate method. The Android implementation avoids this step by using requestFocusNodeHref.

If Navigator is not used, the default implementation of the new delegate method does nothing, so this is a non-breaking change.
@zoejessica
zoejessica requested a review from jayohms January 30, 2026 20:09

@jayohms jayohms left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you @zoejessica! Left one question...

Comment thread Source/Turbo/Navigator/WKUIController.swift Outdated
@svara svara self-assigned this Jun 29, 2026
svara added 6 commits July 1, 2026 13:58
Implement the WKUIDelegate createWebViewWith method so that a new-window
request originating from a non-main frame (e.g. a link inside an iframe
embed) is loaded into the web view instead of being discarded. This lets
taps inside an embed route out of the iframe — for example launching the
YouTube app from an embedded video.

Requests with no URL, or those targeting the main frame, are ignored.
Add a configurable set of invalid schemes (about, data, file, javascript)
to ExternalNavigationWebViewPolicyDecisionHandler. When a matching
navigation carries one of these schemes, cancel it without routing rather
than proposing an external visit.

This guards against routing blank or unsafe URLs — such as those produced
when a new-window request from an iframe is loaded into the web view.
Add tests covering the new external-navigation behaviour: about: and
data: schemes cancel without routing, and a form submission is not
treated as external navigation.
* main: (37 commits)
  Add tests for always-on visit retry handler
  Fix inaccurate log messages in Navigator session inspection
  Set `lazyLoadTabs` to `true` by default.
  Split HotwireLogger into focused files under Logging/
  Move and expand custom logger tests
  Remove obsolete logger.
  Enable debug logging automatically for debug builds in the Demo app.
  Remove obsolete `Logging` in favor of `HotwireLogger`.
  Allow injection of a custom logger
  Make redirect-resolution timeout configurable
  Add ScriptMessage.isReady accessor
  Consolidate Turbo.js status codes into a TurboError type.
  Use `HotwireNativeError` instead of `Error`.
  Fix refresh_historical_location restoring from snapshot cache
  Log HTTP status code on cold boot visit failures
  Add logging to JSFetch recovery handler and error paths
  Retry failed visit directly when no topmost visitable
  Remove isRetryable logic and always provide clients with a retry handler
  Update tests
  Rename to JSFetchRecoveryHandler
  ...
The port to WebViewPolicyNavigationSimulator dropped changes that main
had made to WebViewNavigationSimulator, because the file was renamed.
Reinstate them:

- Cancel the captured navigation (decisionHandler(.cancel)) once the
  continuation resumes, rather than allowing it. Allowing it lets the
  web view navigate to external URLs, blocking the shared WKProcessPool
  and stalling subsequent tests. Intermediate loads are still allowed so
  the page can finish loading and trigger the simulated interaction.
- Add stopLoading() to stop the web view and detach its delegate, now
  called from test tearDown.

The initial about:blank document load is allowed (not cancelled) so
page-initiated navigations can still be captured.
@svara
svara merged commit e3a1d8c into main Jul 1, 2026
1 check passed
@svara
svara deleted the zoe/new-window-navigation branch July 1, 2026 15:28
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.

3 participants