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

Automatically clear data upon quitting #2600

Merged
merged 26 commits into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c7eb278
Settings model
tomasstrba Mar 25, 2024
30a50f0
Disabling restorePreviousSession in case burn data on quit is enabled
tomasstrba Mar 25, 2024
6298998
Label: Disable the Automatically Clear Data setting to enable session…
tomasstrba Mar 25, 2024
b0e1c7f
Burn on Quit functionality
tomasstrba Mar 27, 2024
2198747
Delay for testing
tomasstrba Mar 27, 2024
b657c44
Edge case: unexpected app termination
tomasstrba Mar 29, 2024
d75afc1
Additional button removed
tomasstrba Apr 2, 2024
00631e4
Added clearing data options and warn on burn option
tomasstrba Apr 8, 2024
20efae9
Burn after
tomasstrba Apr 10, 2024
ce50bc0
Changes based on Ship Review
tomasstrba Apr 11, 2024
da7202f
Copy fixed
tomasstrba Apr 11, 2024
857e7ed
Refactoring
tomasstrba Apr 11, 2024
1d8bfff
Unit Tests
tomasstrba Apr 11, 2024
f997036
Merge branch 'main' into tom/automatically-clear-data
tomasstrba Apr 12, 2024
4aa5d52
Copy changed
tomasstrba Apr 12, 2024
c631833
Alert improved based on the ship review
tomasstrba Apr 12, 2024
0a29cbb
Quit without clearing
tomasstrba Apr 12, 2024
2791d02
Changes based on PR and ship review
tomasstrba Apr 18, 2024
1a9317b
Merge branch 'main' into tom/automatically-clear-data
tomasstrba Apr 18, 2024
3cf32e4
Icon replaced
tomasstrba Apr 19, 2024
4f62710
Copy changed
tomasstrba Apr 22, 2024
9761105
More copy changed
tomasstrba Apr 23, 2024
6ca9033
Merge branch 'main' into tom/automatically-clear-data
tomasstrba Apr 23, 2024
2e79aa4
Design review
tomasstrba Apr 25, 2024
b19a275
Translations
tomasstrba Apr 28, 2024
964cbe9
Merge branch 'main' into tom/automatically-clear-data
tomasstrba Apr 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@
1DDF076328F815AD00EDFBE3 /* BWCommunicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDF075D28F815AD00EDFBE3 /* BWCommunicator.swift */; };
1DDF076428F815AD00EDFBE3 /* BWManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDF075E28F815AD00EDFBE3 /* BWManager.swift */; };
1DE03425298BC7F000CAB3D7 /* InternalUserDeciderStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D12F2E1298BC660009A65FD /* InternalUserDeciderStoreMock.swift */; };
1DEF3BAD2BD145A9004A2FBA /* AutoClearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DEF3BAC2BD145A9004A2FBA /* AutoClearHandler.swift */; };
1DEF3BAE2BD145A9004A2FBA /* AutoClearHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DEF3BAC2BD145A9004A2FBA /* AutoClearHandler.swift */; };
1DFAB51D2A8982A600A0F7F6 /* SetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFAB51C2A8982A600A0F7F6 /* SetExtension.swift */; };
1DFAB51E2A8982A600A0F7F6 /* SetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFAB51C2A8982A600A0F7F6 /* SetExtension.swift */; };
1DFAB5222A8983DE00A0F7F6 /* SetExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFAB51F2A89830D00A0F7F6 /* SetExtensionTests.swift */; };
Expand Down Expand Up @@ -2821,6 +2823,7 @@
1DDF075F28F815AD00EDFBE3 /* BWStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWStatus.swift; sourceTree = "<group>"; };
1DDF076028F815AD00EDFBE3 /* BWError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWError.swift; sourceTree = "<group>"; };
1DDF076128F815AD00EDFBE3 /* BWResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWResponse.swift; sourceTree = "<group>"; };
1DEF3BAC2BD145A9004A2FBA /* AutoClearHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoClearHandler.swift; sourceTree = "<group>"; };
1DFAB51C2A8982A600A0F7F6 /* SetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetExtension.swift; sourceTree = "<group>"; };
1DFAB51F2A89830D00A0F7F6 /* SetExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetExtensionTests.swift; sourceTree = "<group>"; };
1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6404,6 +6407,7 @@
858A798226A8B75F00A75A42 /* CopyHandler.swift */,
1D36E65A298ACD2900AA485D /* AppIconChanger.swift */,
CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */,
1DEF3BAC2BD145A9004A2FBA /* AutoClearHandler.swift */,
);
path = Application;
sourceTree = "<group>";
Expand Down Expand Up @@ -10131,6 +10135,7 @@
3706FC96293F65D500E42796 /* HorizontallyCenteredLayout.swift in Sources */,
3706FC97293F65D500E42796 /* BookmarksOutlineView.swift in Sources */,
3706FC98293F65D500E42796 /* CountryList.swift in Sources */,
1DEF3BAE2BD145A9004A2FBA /* AutoClearHandler.swift in Sources */,
4B37EE732B4CFF0800A89A61 /* HomePageRemoteMessagingStorage.swift in Sources */,
3706FC99293F65D500E42796 /* PreferencesSection.swift in Sources */,
B6C8CAA82AD010DD0060E1CD /* YandexDataImporter.swift in Sources */,
Expand Down Expand Up @@ -10952,6 +10957,7 @@
37054FCE2876472D00033B6F /* WebViewSnapshotView.swift in Sources */,
4BBC16A027C4859400E00A38 /* DeviceAuthenticationService.swift in Sources */,
CB24F70C29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift in Sources */,
1DEF3BAD2BD145A9004A2FBA /* AutoClearHandler.swift in Sources */,
37CBCA9A2A8966E60050218F /* SyncSettingsAdapter.swift in Sources */,
EEC4A66D2B2C894F00F7C0AA /* VPNLocationPreferenceItemModel.swift in Sources */,
3776582F27F82E62009A6B35 /* AutofillPreferences.swift in Sources */,
Expand Down
19 changes: 19 additions & 0 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
let internalUserDecider: InternalUserDecider
let featureFlagger: FeatureFlagger
private var appIconChanger: AppIconChanger!
private var autoClearHandler: AutoClearHandler!

private(set) var syncDataProviders: SyncDataProviders!
private(set) var syncService: DDGSyncing?
Expand Down Expand Up @@ -315,6 +316,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
#if DBP
DataBrokerProtectionAppEvents().applicationDidFinishLaunching()
#endif

setUpAutoClearHandler()
}

func applicationDidBecomeActive(_ notification: Notification) {
Expand Down Expand Up @@ -352,6 +355,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
}
stateRestorationManager?.applicationWillTerminate()

// Handling of "Burn on quit"
if let terminationReply = autoClearHandler.handleAppTermination() {
return terminationReply
}

return .terminateNow
}

Expand Down Expand Up @@ -550,6 +558,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
PixelKit.fire(GeneralPixel.importDataInitial, frequency: .legacyInitial)
}
}

private func setUpAutoClearHandler() {
autoClearHandler = AutoClearHandler(preferences: .shared,
fireViewModel: FireCoordinator.fireViewModel,
stateRestorationManager: stateRestorationManager)
autoClearHandler.handleAppLaunch()
autoClearHandler.onAutoClearCompleted = {
NSApplication.shared.reply(toApplicationShouldTerminate: true)
}
}

}

func updateSubscriptionStatus() {
Expand Down
125 changes: 125 additions & 0 deletions DuckDuckGo/Application/AutoClearHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// AutoClearHandler.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation
import Combine

final class AutoClearHandler {

private let preferences: DataClearingPreferences
private let fireViewModel: FireViewModel
private let stateRestorationManager: AppStateRestorationManager

init(preferences: DataClearingPreferences,
fireViewModel: FireViewModel,
stateRestorationManager: AppStateRestorationManager) {
self.preferences = preferences
self.fireViewModel = fireViewModel
self.stateRestorationManager = stateRestorationManager
}

@MainActor
func handleAppLaunch() {
burnOnStartIfNeeded()
restoreTabsIfNeeded()
resetTheCorrectTerminationFlag()
}

var onAutoClearCompleted: (() -> Void)?

@MainActor
func handleAppTermination() -> NSApplication.TerminateReply? {
guard preferences.isAutoClearEnabled else { return nil }

if preferences.isWarnBeforeClearingEnabled {
switch confirmAutoClear() {
case .alertFirstButtonReturn:
tomasstrba marked this conversation as resolved.
Show resolved Hide resolved
// Clear and Quit
performAutoClear()
return .terminateLater
case .alertSecondButtonReturn:
// Quit without Clearing Data
appTerminationHandledCorrectly = true
restoreTabsOnStartup = true
return .terminateNow
default:
// Cancel
return .terminateCancel
}
}

performAutoClear()
return .terminateLater
}

func resetTheCorrectTerminationFlag() {
appTerminationHandledCorrectly = false
}

// MARK: - Private

private func confirmAutoClear() -> NSApplication.ModalResponse {
let alert = NSAlert.autoClearAlert()
let response = alert.runModal()
return response
}

@MainActor
private func performAutoClear() {
fireViewModel.fire.burnAll { [weak self] in
self?.appTerminationHandledCorrectly = true
self?.onAutoClearCompleted?()
}
}

// MARK: - Burn On Start
// Burning on quit wasn't successful

@UserDefaultsWrapper(key: .appTerminationHandledCorrectly, defaultValue: false)
private var appTerminationHandledCorrectly: Bool

@MainActor
@discardableResult
func burnOnStartIfNeeded() -> Bool {
let shouldBurnOnStart = preferences.isAutoClearEnabled && !appTerminationHandledCorrectly
guard shouldBurnOnStart else { return false }

fireViewModel.fire.burnAll()
return true
}

// MARK: - Burn without Clearing Data

@UserDefaultsWrapper(key: .restoreTabsOnStartup, defaultValue: false)
private var restoreTabsOnStartup: Bool

@MainActor
@discardableResult
func restoreTabsIfNeeded() -> Bool {
let isAutoClearEnabled = preferences.isAutoClearEnabled
let restoreTabsOnStartup = restoreTabsOnStartup
self.restoreTabsOnStartup = false
if isAutoClearEnabled && restoreTabsOnStartup {
stateRestorationManager.restoreLastSessionState(interactive: false)
return true
}

return false
}

}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "Burn-Original-large.pdf",
"filename" : "Fire-96x96.pdf",
"idiom" : "universal"
}
],
Expand Down
Binary file not shown.
31 changes: 31 additions & 0 deletions DuckDuckGo/Common/Extensions/NSAlertExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,37 @@ extension NSAlert {
return alert
}

static func autoClearAlert() -> NSAlert {
let alert = NSAlert()
alert.messageText = UserText.warnBeforeQuitDialogHeader
alert.alertStyle = .warning
alert.icon = .burnAlert
alert.addButton(withTitle: UserText.clearAndQuit)
alert.addButton(withTitle: UserText.quitWithoutClearing)
alert.addButton(withTitle: UserText.cancel)

let checkbox = NSButton(checkboxWithTitle: UserText.warnBeforeQuitDialogCheckboxMessage,
target: DataClearingPreferences.shared,
action: #selector(DataClearingPreferences.toggleWarnBeforeClearing))
checkbox.state = DataClearingPreferences.shared.isWarnBeforeClearingEnabled ? .on : .off
checkbox.lineBreakMode = .byWordWrapping
checkbox.translatesAutoresizingMaskIntoConstraints = false

// Create a container view for the checkbox with custom padding
let containerView = NSView(frame: NSRect(x: 0, y: 0, width: 240, height: 25))
containerView.addSubview(checkbox)

NSLayoutConstraint.activate([
checkbox.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
tomasstrba marked this conversation as resolved.
Show resolved Hide resolved
checkbox.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: -10), // Slightly up for better visual alignment
checkbox.widthAnchor.constraint(lessThanOrEqualTo: containerView.widthAnchor)
])

alert.accessoryView = containerView

return alert
}

@discardableResult
func runModal() async -> NSApplication.ModalResponse {
await withCheckedContinuation { continuation in
Expand Down
13 changes: 13 additions & 0 deletions DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ struct UserText {
static let pasteAndGo = NSLocalizedString("paste.and.go", value: "Paste & Go", comment: "Paste & Go button")
static let pasteAndSearch = NSLocalizedString("paste.and.search", value: "Paste & Search", comment: "Paste & Search button")
static let clear = NSLocalizedString("clear", value: "Clear", comment: "Clear button")
static let clearAndQuit = NSLocalizedString("clear.and.quit", value: "Clear and Quit", comment: "Button to clear data and quit the application")
static let quitWithoutClearing = NSLocalizedString("quit.without.clearing", value: "Quit Without Clearing", comment: "Button to quit the application without clearing data")
static let `continue` = NSLocalizedString("`continue`", value: "Continue", comment: "Continue button")
static let bookmarkDialogAdd = NSLocalizedString("bookmark.dialog.add", value: "Add", comment: "Button to confim a bookmark creation")
static let newFolderDialogAdd = NSLocalizedString("folder.dialog.add", value: "Add", comment: "Button to confim a bookmark folder creation")
Expand Down Expand Up @@ -1077,6 +1079,17 @@ struct UserText {
static let fireproofCheckboxTitle = NSLocalizedString("fireproof.checkbox.title", value: "Ask to Fireproof websites when signing in", comment: "Fireproof settings checkbox title")
static let fireproofExplanation = NSLocalizedString("fireproof.explanation", value: "When you Fireproof a site, cookies won't be erased and you'll stay signed in, even after using the Fire Button.", comment: "Fireproofing mechanism explanation")
static let manageFireproofSites = NSLocalizedString("fireproof.manage-sites", value: "Manage Fireproof Sites…", comment: "Fireproof settings button caption")
static let autoClear = NSLocalizedString("auto.clear", value: "Auto-Clear", comment: "Header of a section in Settings. The setting configures clearing data automatically after quitting the app.")
static let automaticallyClearData = NSLocalizedString("automatically.clear.data", value: "Automatically clear tabs and browsing data when DuckDuckGo quits", comment: "Label after the checkbox in Settings which configures clearing data automatically after quitting the app.")
static let warnBeforeQuit = NSLocalizedString("warn.before.quit", value: "Warn me that tabs and data will be cleared when quitting", comment: "Label after the checkbox in Settings which configures a warning before clearing data on the application termination.")
static let warnBeforeQuitDialogHeader = NSLocalizedString("warn.before.quit.dialog.header", value: "Clear tabs and browsing data and quit DuckDuckGo?", comment: "A header of warning before clearing data on the application termination.")
static let warnBeforeQuitDialogCheckboxMessage = NSLocalizedString("warn.before.quit.dialog.checkbox.message", value: "Warn me every time", comment: "A label after checkbox to configure the warning before clearing data on the application termination.")
static let disableAutoClearToEnableSessionRestore = NSLocalizedString("disable.auto.clear.to.enable.session.restore",
value: "Disable auto-clear on quit to turn on session restore.",
comment: "Information label in Settings. It tells user that to enable session restoration setting they have to disable burn on quit. Auto-Clear should match the string with 'auto.clear' key")
static let showDataClearingSettings = NSLocalizedString("show.data.clearing.settings",
value: "Open Data Clearing Settings",
comment: "Button in Settings. It navigates user to Data Clearing Settings. The Data Clearing string should match the string with the preferences.data-clearing key")

// MARK: Crash Report
static let crashReportTitle = NSLocalizedString("crash-report.title", value: "DuckDuckGo Privacy Browser quit unexpectedly.", comment: "Title of the dialog where the user can send a crash report")
Expand Down
4 changes: 4 additions & 0 deletions DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public struct UserDefaultsWrapper<T> {
case grammarCheckEnabledOnce = "grammar.check.enabled.once"

case loginDetectionEnabled = "fireproofing.login-detection-enabled"
case autoClearEnabled = "preferences.auto-clear-enabled"
case warnBeforeClearingEnabled = "preferences.warn-before-clearing-enabled"
case gpcEnabled = "preferences.gpc-enabled"
case selectedDownloadLocationKey = "preferences.download-location"
case lastUsedCustomDownloadLocation = "preferences.custom-last-used-download-location"
Expand All @@ -78,6 +80,8 @@ public struct UserDefaultsWrapper<T> {
case lastCrashReportCheckDate = "last.crash.report.check.date"

case fireInfoPresentedOnce = "fire.info.presented.once"
case appTerminationHandledCorrectly = "app.termination.handled.correctly"
case restoreTabsOnStartup = "restore.tabs.on.startup"

case restorePreviousSession = "preferences.startup.restore-previous-session"
case launchToCustomHomePage = "preferences.startup.launch-to-custom-home-page"
Expand Down
Loading
Loading