feat(paywalls): web_view component view + view model, not yet wired (6/7)#7232
Conversation
Add PaywallWebViewMessage/Value/Controller/Action types and API baselines. Controller post APIs are no-ops until the session PR wires WebKit. Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
Add WebViewEnvelope encode/decode and input hardening tests. Inert until Part 7; builds on the public API from Part 1. Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
…3/7) Extract origin normalization, main-frame navigation policy, and WK content rule isolation as inert helpers used by later session/view PRs. Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
Add PaywallComponent.WebViewComponent as dead code without registering ComponentType.webView or PaywallComponent.webView, so paywall JSON is unchanged until the activation PR. Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
… (5/7) Add WebViewSession (using WebViewOrigin), restore controller session wiring, and document-reset so reloads can re-handshake. Includes session tests plus reconnect/resize-threshold/onDocumentReset coverage. Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
…6/7) Add WebViewComponentView/ViewModel with document-reset and content-process termination lifecycle, fit-axes identity, and coordinator tests. Still unreferenced by the render pipeline until Part 7. Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
|
Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
4 builds increased size
RevenueCat 1.0 (1)
|
| Item | Install Size Change |
|---|---|
| DYLD.String Table | ⬆️ 39.5 kB |
| Code Signature | ⬆️ 4.9 kB |
| RevenueCatUI.Coordinator.Objc Metadata | ⬆️ 4.6 kB |
| DYLD.Exports | ⬆️ 3.1 kB |
| External Methods | ⬆️ 1.5 kB |
BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.local-source
⚖️ Compare build
📦 Install build
⏱️ Analyze build performance
Total install size change: ⬆️ 8.9 kB (0.07%)
Total download size change: ⬆️ 3.7 kB (0.08%)
Largest size changes
| Item | Install Size Change |
|---|---|
| 📝 WKNavigationDelegate.Objc Metadata | ⬆️ 2.6 kB |
| DYLD.String Table | ⬆️ 1.3 kB |
| 📝 RevenueCatUI.WebViewNavigationPolicy.policy(for,isMainFrame,expec... | ⬆️ 648 B |
| RevenueCatUI.Coordinator.Objc Metadata | ⬆️ 642 B |
| 📝 RevenueCatUI.WebViewComponentViewModel.Objc Metadata | ⬆️ 548 B |
BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.cocoapods
⚖️ Compare build
📦 Install build
⏱️ Analyze build performance
Total install size change: ⬆️ 21.3 kB (0.07%)
Total download size change: ⬆️ 4.8 kB (0.07%)
Largest size changes
| Item | Install Size Change |
|---|---|
| DYLD.String Table | ⬆️ 6.1 kB |
| 📝 WKNavigationDelegate.Objc Metadata | ⬆️ 2.6 kB |
| 📝 RevenueCatUI.WebViewNavigationPolicy.policy(for,isMainFrame,expec... | ⬆️ 672 B |
| RevenueCatUI.Coordinator.Objc Metadata | ⬆️ 642 B |
| 📝 RevenueCatUI.WebViewComponentViewModel.Objc Metadata | ⬆️ 548 B |
BinarySizeTest 1.0 (1)
com.revenuecat.binary-size-test.spm
⚖️ Compare build
📦 Install build
⏱️ Analyze build performance
Total install size change: ⬆️ 8.6 kB (0.07%)
Total download size change: ⬇️ 1.9 kB (-0.04%)
Largest size changes
| Item | Install Size Change |
|---|---|
| 📝 WKNavigationDelegate.Objc Metadata | ⬆️ 2.5 kB |
| 📝 RevenueCatUI.WebViewNavigationPolicy.policy(for,isMainFrame,expec... | ⬆️ 648 B |
| RevenueCatUI.Coordinator.Objc Metadata | ⬆️ 619 B |
| 📝 RevenueCatUI.WebViewComponentViewModel.Objc Metadata | ⬆️ 548 B |
| 📝 RevenueCatUI.WebViewRepresentable.Coordinator.webView(decidePolic... | ⬆️ 536 B |
🛸 Powered by Emerge Tools
Comment trigger: Size diff threshold of 100.00kB exceeded
Co-authored-by: Jacob Rakidzich <Jacob@JacobZivanDesign.com>
…m guard Co-authored-by: Cursor <cursoragent@cursor.com>
…port order Co-authored-by: Cursor <cursoragent@cursor.com>
Follow-up to merging the refactored base into webview/6-view: - Use the new `URL.webViewOrigin` extension instead of the now-private `WebViewOrigin.origin(of:)` in WebViewComponentView. - Add the copyright header to WebViewComponentView/ViewModel to match the sibling web_view files.
Normalize expectedOrigin to an optional canonical origin; when it cannot be resolved, keep it nil and log a warning instead of silently trusting the raw string, so the bridge rejects all inbound/outbound traffic and the misconfiguration is diagnosable. Mirrors purchases-android. Also splits the outbound skip log into distinct reasons (channel-not-open vs untrusted-current-url), documents why handshake replies allow sending before the top-level URL settles, and fixes a stale render-only test comment. Co-authored-by: Cursor <cursoragent@cursor.com>
Brings in the updated base commit "treat unresolvable web_view origin as inert bridge" (WebViewSession.expectedOrigin is now optional). - Strings.swift: unioned the two sides — keep the new post_message_skipped(reason:) and invalid_expected_origin cases from the base, and retain content_process_terminated used by the Part 6 view. - WebViewComponentView: pass session.expectedOrigin ?? "" to the representable so an unresolvable origin also cancels main-frame navigation, keeping the whole web view inert (not just the bridge).
…onnect Pass only the protocol version into handleConnect (matching handleResize), annotate the JS line-terminator escaping, and trim comment wording. Co-authored-by: Cursor <cursoragent@cursor.com>
…inbound-drop assertions Add coverage for component-id mismatch, unknown message type, missing type, and the wrong-channel decode filter. Rework the existing inbound-drop tests to assert against an onContentResize sink with a real payload instead of captured outbound scripts, so a resize that should be dropped is distinguishable from a no-op and the tests fail if their gate is removed. Co-authored-by: Cursor <cursoragent@cursor.com>
- Drop unused `localizationProvider` from WebViewComponentViewModel.init - Include `size` and `visible` in the view model's equality/hash so rendered-state differences aren't treated as equal - Cache `url` validation via a lazy var - Drop the redundant BridgedWebViewComponentView.componentID property - Isolate the navigation-delegate Coordinator to @mainactor and drop the per-method MainActor.assumeIsolated wrappers - Add view-model equality/visible tests and an empty-origin navigation policy case Co-authored-by: Cursor <cursoragent@cursor.com>
…ses-ios into webview/6-view
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # RevenueCat.xcodeproj/project.pbxproj # RevenueCatUI/Data/Strings.swift
…rget The consolidated WebViewComponentTests now imports RevenueCatUI, but the xcodeproj UnitTests target only links RevenueCat, causing "Unable to find module dependency: 'RevenueCatUI'". Remove it from that target's Sources so it runs only via the SPM RevenueCatUITests target, matching its RevenueCatUI-importing siblings (WebViewSessionTests, WebViewEnvelopeTests). Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2f1d7ca. Configure here.
WebViewComponentTests mixed RevenueCat schema (Codable) tests with RevenueCatUI view-model/coordinator tests in a single file. Split them to match the Video component convention: - Tests/UnitTests/Paywalls/Components/WebViewComponentTests.swift: schema decoding tests (RevenueCat only), wired into the xcodeproj UnitTests target so they run under `fastlane scan` again. - Tests/RevenueCatUITests/PaywallsV2/WebViewComponentViewTests.swift: view-model/sizing/coordinator tests (RevenueCatUI), SPM only. Co-authored-by: Cursor <cursoragent@cursor.com>
The private makeWebView/update/configureSession helpers were non-isolated, so mutating the @mainactor WebViewSession properties from them failed to compile under Xcode 15 (Swift 5.9) in the SPM RevenueCatUI release build. Annotate the helpers @mainactor to keep the main-actor chain intact. Co-authored-by: Cursor <cursoragent@cursor.com>
AlvaroBrey
left a comment
There was a problem hiding this comment.
Nothing blocking, just a nit and a question
| #if os(macOS) | ||
| webView.configuration.websiteDataStore.removeData( | ||
| ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), | ||
| modifiedSince: .distantPast | ||
| ) {} | ||
| #endif |
There was a problem hiding this comment.
Good catch!
Tbh, I'm not sure. This was pre-existent and I couldn't find any reason why this would be needed, since we have a per-view nonPersistent() store, it doesn't write data to disk, it writes it in-memory, and the data is released when the web view + its configuration deallocates anyway.
I removed this for now in fb5e589. We can re-add it if needed.
@JZDesign do you know why this was done and only for macOS?
The web view uses a per-view .nonPersistent() data store, which keeps data in memory and releases it when the web view deallocates, so the macOS-only removeData call on teardown was redundant. Co-authored-by: Cursor <cursoragent@cursor.com>





Checklist
purchases-androidand hybridsMotivation
Adds the
web_viewpaywall component's SwiftUI view and view model. It renders a sandboxedWKWebViewbut is not yet wired into component rendering, so it has no runtime effect yet.Description