Skip to content

Commit

Permalink
Persist Interaction State for tabs (#3824)
Browse files Browse the repository at this point in the history
<!--
Note: This checklist is a reminder of our shared engineering
expectations. Feel free to change it, although assigning a GitHub
reviewer and the items in bold are required.

⚠️ If you're an external contributor, please file an issue first before
working on a PR, as we can't guarantee that we will accept your changes
if they haven't been discussed ahead of time. Thanks!
-->

Task/Issue URL: https://app.asana.com/0/0/1208991633646641/f
Tech Design URL:
https://app.asana.com/0/481882893211075/1208983861756006/f
CC:

**Description**:

Adds storage and automatically stores WKWebView's interaction state data
for each tab. These are used to restore back/forward history and scroll
position on next launch after app was terminated.

**Steps to test this PR**:
1. Mark yourself as internal user to enable the feature. Restart is
required for the flag to have an effect.
1. Open a few tabs, navigate to various links so that navigating
back/forward is possible. Scroll can be adjusted.
2. Terminate the app. 
3. Verify last opened tab is visible and website is scrolled to
(roughly) the same position on the screen.
4. Verify navigation history is available and working properly.
5. Use Fire button. Verify all cache files are being removed. You can
check the directory by putting a breakpoint somewhere in
`TabInteractionStateDiskSource` and printing
`interactionStateCacheLocation` or use debug menu to see the list of
cache files.
6. Open a few tabs again, enable Automatic Data Clearing.
7. Verify all interaction cache files are removed.

<!--
Before submitting a PR, please ensure you have tested the combinations
you expect the reviewer to test, then delete configurations you *know*
do not need explicit testing.

Using a simulator where a physical device is unavailable is acceptable.
-->

**Definition of Done (Internal Only)**:

* [ ] Does this PR satisfy our [Definition of
Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)?

---
###### Internal references:
[Software Engineering
Expectations](https://app.asana.com/0/59792373528535/199064865822552)
[Technical Design
Template](https://app.asana.com/0/59792373528535/184709971311943)
  • Loading branch information
dus7 authored Jan 21, 2025
1 parent c17275e commit c7f080e
Show file tree
Hide file tree
Showing 18 changed files with 662 additions and 25 deletions.
5 changes: 5 additions & 0 deletions Core/FeatureFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ public enum FeatureFlag: String {

/// https://app.asana.com/0/0/1208767141940869/f
case privacyProFreeTrialJan25

/// https://app.asana.com/0/1206226850447395/1206307878076518
case webViewStateRestoration
}

extension FeatureFlag: FeatureFlagDescribing {
Expand Down Expand Up @@ -146,6 +149,8 @@ extension FeatureFlag: FeatureFlagDescribing {
return .remoteReleasable(.subfeature(AIChatSubfeature.deepLink))
case .tabManagerMultiSelection:
return .internalOnly()
case .webViewStateRestoration:
return .internalOnly()
}
}
}
Expand Down
20 changes: 19 additions & 1 deletion Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,12 @@ extension Pixel {
// MARK: Lifecycle
case appDidTransitionToUnexpectedState

// MARK: Tab interaction state debug pixels
case tabInteractionStateSourceMissingRootDirectory
case tabInteractionStateSourceFailedToWrite

case tabInteractionStateFailedToRestore
case tabInteractionStateRestorationTime(_ time: BucketAggregation)
}

}
Expand Down Expand Up @@ -1560,8 +1566,20 @@ extension Pixel.Event {
case .debugWebsiteDataStoresNotClearedOne: return "m_d_wkwebsitedatastoresnotcleared_one"
case .debugWebsiteDataStoresCleared: return "m_d_wkwebsitedatastorescleared"

// MARK: Tab interaction state debug pixels

case .tabInteractionStateSourceMissingRootDirectory:
return "m_d_tab-interaction-state-source_missing-root-directory"
case .tabInteractionStateSourceFailedToWrite:
return "m_d_tab-interaction-state-source_failed-to-write"

case .tabInteractionStateFailedToRestore:
return "m_d_tab-interaction-state_failed-to-restore"
case .tabInteractionStateRestorationTime(let aggregation):
return "m_d_tab-interaction-state_restoration-time-\(aggregation)"

// MARK: Ad Attribution

case .adAttributionGlobalAttributedRulesDoNotExist: return "m_attribution_global_attributed_rules_do_not_exist"
case .adAttributionCompilationFailedForAttributedRulesList: return "m_attribution_compilation_failed_for_attributed_rules_list"

Expand Down
1 change: 1 addition & 0 deletions Core/UserDefaultsPropertyWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public struct UserDefaultsWrapper<T> {
// Debug keys
case debugNewTabPageSectionsEnabledKey = "com.duckduckgo.ios.debug.newTabPageSectionsEnabled"
case debugOnboardingHighlightsEnabledKey = "com.duckduckgo.ios.debug.onboardingHighlightsEnabled"
case debugWebViewStateRestorationEnabledKey = "com.duckduckgo.ios.debug.webViewStateRestorationEnabled"

// Duck Player Pixel Experiment
case duckPlayerPixelExperimentInstalled = "com.duckduckgo.ios.duckplayer.pixel.experiment.installed.v2"
Expand Down
24 changes: 24 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@
6F03CB092C32F331004179A8 /* PixelFiringAsync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F03CB082C32F331004179A8 /* PixelFiringAsync.swift */; };
6F0FEF6B2C516D540090CDE4 /* NewTabPageSettingsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0FEF6A2C516D540090CDE4 /* NewTabPageSettingsStorage.swift */; };
6F0FEF6D2C52639E0090CDE4 /* ReorderableForEach.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0FEF6C2C52639E0090CDE4 /* ReorderableForEach.swift */; };
6F1422822D314A5300B6D3DE /* TabInteractionStateDiskSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1422812D314A5300B6D3DE /* TabInteractionStateDiskSource.swift */; };
6F1422842D314DD100B6D3DE /* TabInteractionStateDiskSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1422832D314DC900B6D3DE /* TabInteractionStateDiskSourceTests.swift */; };
6F1422862D31509500B6D3DE /* MockDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1422852D31509000B6D3DE /* MockDataExtensions.swift */; };
6F3529FF2CDCEDFF00A59170 /* OmniBarLoadingStateBearerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3529FE2CDCEDF700A59170 /* OmniBarLoadingStateBearerTests.swift */; };
6F35379E2C4AAF2E009F8717 /* NewTabPageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F35379D2C4AAF2E009F8717 /* NewTabPageSettingsView.swift */; };
6F3537A02C4AAFD2009F8717 /* NewTabPageSettingsSectionItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F35379F2C4AAFD2009F8717 /* NewTabPageSettingsSectionItemView.swift */; };
Expand All @@ -332,6 +335,9 @@
6F64AA5F2C49463C00CF4489 /* ShortcutsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F64AA5E2C49463C00CF4489 /* ShortcutsModel.swift */; };
6F655BE22BAB289E00AC3597 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */; };
6F691CCA2C4979EC002E9553 /* FavoritesTooltip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F691CC92C4979EC002E9553 /* FavoritesTooltip.swift */; };
6F72353D2D3EBCB800710C07 /* WebViewStateRestorationDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F72353C2D3EBCB000710C07 /* WebViewStateRestorationDebugView.swift */; };
6F72353F2D3EC11E00710C07 /* WebViewStateRestorationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F72353E2D3EC11500710C07 /* WebViewStateRestorationManager.swift */; };
6F7235412D3F974600710C07 /* MockTabInteractionStateSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7235402D3F973C00710C07 /* MockTabInteractionStateSource.swift */; };
6F7BACD42CEE084B00F561D8 /* OmniBarEqualityCheckTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7BACD32CEE084100F561D8 /* OmniBarEqualityCheckTests.swift */; };
6F7FB8E12C660B3E00867DA7 /* NewTabPageFavoritesModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8DF2C660B1A00867DA7 /* NewTabPageFavoritesModelTests.swift */; };
6F7FB8E32C660BF300867DA7 /* DailyPixelFiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8E22C660BF300867DA7 /* DailyPixelFiring.swift */; };
Expand Down Expand Up @@ -1715,6 +1721,9 @@
6F03CB082C32F331004179A8 /* PixelFiringAsync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelFiringAsync.swift; sourceTree = "<group>"; };
6F0FEF6A2C516D540090CDE4 /* NewTabPageSettingsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSettingsStorage.swift; sourceTree = "<group>"; };
6F0FEF6C2C52639E0090CDE4 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = "<group>"; };
6F1422812D314A5300B6D3DE /* TabInteractionStateDiskSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabInteractionStateDiskSource.swift; sourceTree = "<group>"; };
6F1422832D314DC900B6D3DE /* TabInteractionStateDiskSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabInteractionStateDiskSourceTests.swift; sourceTree = "<group>"; };
6F1422852D31509000B6D3DE /* MockDataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDataExtensions.swift; sourceTree = "<group>"; };
6F3529FE2CDCEDF700A59170 /* OmniBarLoadingStateBearerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBarLoadingStateBearerTests.swift; sourceTree = "<group>"; };
6F35379D2C4AAF2E009F8717 /* NewTabPageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSettingsView.swift; sourceTree = "<group>"; };
6F35379F2C4AAFD2009F8717 /* NewTabPageSettingsSectionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSettingsSectionItemView.swift; sourceTree = "<group>"; };
Expand All @@ -1734,6 +1743,9 @@
6F64AA5E2C49463C00CF4489 /* ShortcutsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsModel.swift; sourceTree = "<group>"; };
6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = "<group>"; };
6F691CC92C4979EC002E9553 /* FavoritesTooltip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesTooltip.swift; sourceTree = "<group>"; };
6F72353C2D3EBCB000710C07 /* WebViewStateRestorationDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewStateRestorationDebugView.swift; sourceTree = "<group>"; };
6F72353E2D3EC11500710C07 /* WebViewStateRestorationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewStateRestorationManager.swift; sourceTree = "<group>"; };
6F7235402D3F973C00710C07 /* MockTabInteractionStateSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTabInteractionStateSource.swift; sourceTree = "<group>"; };
6F7BACD32CEE084100F561D8 /* OmniBarEqualityCheckTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBarEqualityCheckTests.swift; sourceTree = "<group>"; };
6F7FB8DF2C660B1A00867DA7 /* NewTabPageFavoritesModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageFavoritesModelTests.swift; sourceTree = "<group>"; };
6F7FB8E22C660BF300867DA7 /* DailyPixelFiring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixelFiring.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4779,6 +4791,7 @@
D6F93E3B2B4FFA97004C268D /* SubscriptionDebugViewController.swift */,
8590CB602684D0600089F6BF /* CookieDebugViewController.swift */,
31206F6F2D3804E800A95D76 /* AIChatDebugView.swift */,
6F72353C2D3EBCB000710C07 /* WebViewStateRestorationDebugView.swift */,
4B0295182537BC6700E00CEF /* ConfigurationDebugViewController.swift */,
858566FA252E55D6007501B8 /* ImageCacheDebugViewController.swift */,
8590CB66268A2E520089F6BF /* RootDebugViewController.swift */,
Expand Down Expand Up @@ -6306,6 +6319,8 @@
F13B4BF41F18C74500814661 /* Tabs */ = {
isa = PBXGroup;
children = (
6F72353E2D3EC11500710C07 /* WebViewStateRestorationManager.swift */,
6F1422812D314A5300B6D3DE /* TabInteractionStateDiskSource.swift */,
8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */,
F1617C141E57336D00DEDCAF /* TabManager.swift */,
F13B4BF51F18C75D00814661 /* Model */,
Expand Down Expand Up @@ -6355,6 +6370,7 @@
F13B4BF71F18C9E800814661 /* Tabs */ = {
isa = PBXGroup;
children = (
6F1422832D314DC900B6D3DE /* TabInteractionStateDiskSourceTests.swift */,
6FF9AD402CE6610600C5A406 /* TabSwitcherOpenDailyPixelTests.swift */,
85010503292FFB080033978F /* FireproofFaviconUpdaterTests.swift */,
8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */,
Expand Down Expand Up @@ -6506,6 +6522,8 @@
F17669A91E412A17003D3222 /* Mocks */ = {
isa = PBXGroup;
children = (
6F7235402D3F973C00710C07 /* MockTabInteractionStateSource.swift */,
6F1422852D31509000B6D3DE /* MockDataExtensions.swift */,
85C503FE2CF0F4D60075DF6F /* MockWebsiteDataManager.swift */,
8598D2E52CEBAA1B00C45685 /* MockFaviconStore.swift */,
9F4CC51A2C48C0C7006A96EB /* MockTabDelegate.swift */,
Expand Down Expand Up @@ -8220,6 +8238,7 @@
6FDC64052C98515E00DB71B3 /* FavoriteAddItemView.swift in Sources */,
982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */,
D6E0C1852B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift in Sources */,
6F72353D2D3EBCB800710C07 /* WebViewStateRestorationDebugView.swift in Sources */,
9F8E0F312CCA6390001EA7C5 /* AddToDockTutorialView.swift in Sources */,
D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */,
9F8E0F2F2CCA6202001EA7C5 /* VideoPlayerViewModel.swift in Sources */,
Expand Down Expand Up @@ -8350,6 +8369,7 @@
D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */,
1DEAADEE2BA45DFE00E25A97 /* SettingsDataClearingView.swift in Sources */,
6F96FF102C2B128500162692 /* NewTabPageCustomizeButtonView.swift in Sources */,
6F1422822D314A5300B6D3DE /* TabInteractionStateDiskSource.swift in Sources */,
EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */,
7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */,
9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */,
Expand Down Expand Up @@ -8395,6 +8415,7 @@
85C2971A248162CA0063A335 /* DaxOnboardingPadViewController.swift in Sources */,
F4F6DFB826EA9AA600ED7E12 /* BookmarksTextFieldCell.swift in Sources */,
6FE127402C204D9B00EB5724 /* ShortcutsView.swift in Sources */,
6F72353F2D3EC11E00710C07 /* WebViewStateRestorationManager.swift in Sources */,
85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */,
84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */,
310D091D2799F57200DC0060 /* Download.swift in Sources */,
Expand Down Expand Up @@ -8613,6 +8634,7 @@
987130C5294AAB9F00AB05E0 /* BookmarkEditorViewModelTests.swift in Sources */,
BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */,
9F8E0F332CCA642D001EA7C5 /* VideoPlayerViewModelTests.swift in Sources */,
6F1422842D314DD100B6D3DE /* TabInteractionStateDiskSourceTests.swift in Sources */,
D62EC3BA2C246A7000FC9D04 /* YoutublePlayerNavigationHandlerTests.swift in Sources */,
1EAABE712C99FC75003F5137 /* SubscriptionFeatureAvailabilityMock.swift in Sources */,
8341D807212D5E8D000514C2 /* HashExtensionTest.swift in Sources */,
Expand Down Expand Up @@ -8663,6 +8685,7 @@
56A061442BEE086700F24B36 /* CapturingAdapterErrorHandler.swift in Sources */,
C1BF0BA929B63E2200482B73 /* AutofillLoginPromptViewModelTests.swift in Sources */,
EE3B226B29DE0F110082298A /* MockInternalUserStoring.swift in Sources */,
6F1422862D31509500B6D3DE /* MockDataExtensions.swift in Sources */,
852409312C78030D00CB28FC /* MockUsageSegmentation.swift in Sources */,
987130C8294AAB9F00AB05E0 /* BookmarksTestHelpers.swift in Sources */,
9F4CC51D2C48D240006A96EB /* CoreDataDatabaseTestUtilities.swift in Sources */,
Expand Down Expand Up @@ -8696,6 +8719,7 @@
C1935A222C89CA9F001AD72D /* AutofillSurveyManagerTests.swift in Sources */,
22CB1ED8203DDD2C00D2C724 /* AppDeepLinksTests.swift in Sources */,
9847C00527A41A0A00DB07AA /* WebViewTestHelper.swift in Sources */,
6F7235412D3F974600710C07 /* MockTabInteractionStateSource.swift in Sources */,
3170048227A9504F00C03F35 /* DownloadMocks.swift in Sources */,
317045C02858C6B90016ED1F /* AutofillInterfaceEmailTruncatorTests.swift in Sources */,
6FD3AEE32B8F4EEB0060FCCC /* AdAttributionPixelReporterTests.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions DuckDuckGo/AppLifecycle/AppStates/Launching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,11 @@ struct Launching: AppState {
await autoClear.clearDataIfEnabled(applicationState: .init(with: applicationState))
await vpnWorkaround.installRedditSessionWorkaround()
}

if !autoClear.isClearingEnabled {
// If not using autoclear, make sure there are no leftover states on disk.
mainViewController?.tabManager.removeLeftoverInteractionStates()
}
}
unService = UNService(window: window, accountManager: accountManager)
uiService = UIService(window: window)
Expand Down
Loading

0 comments on commit c7f080e

Please sign in to comment.