Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fix #1358: Cookie consent blocking (#5839)
Browse files Browse the repository at this point in the history
Co-authored-by: Soner Yuksel <grkmyksl@gmail.com>
  • Loading branch information
cuba and soner-yuksel authored Nov 2, 2022
1 parent d70a0cc commit df087be
Show file tree
Hide file tree
Showing 23 changed files with 562 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"data" : [
{
"filename" : "dark-mode.gif",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"data" : [
{
"filename" : "light-mode.gif",
"idiom" : "universal",
"universal-type-identifier" : "com.compuserve.gif"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Background@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Background@2x-1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Background@3x.png",
"idiom" : "universal",
"scale" : "3x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "Background@3x-1.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "original"
}
}
1 change: 1 addition & 0 deletions Client/Frontend/Browser/BrowserViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,7 @@ public class BrowserViewController: UIViewController {
self.presentVPNAlertCallout()
self.presentDefaultBrowserScreenCallout()
self.presentBraveRewardsScreenCallout()
self.presentCookieNotificationBlockingCalloutIfNeeded()
}

screenshotHelper.viewIsVisible = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2022 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import Foundation
import SwiftUI
import BraveUI
import BraveShared
import Data

extension BrowserViewController {
func presentCookieNotificationBlockingCalloutIfNeeded() {
// Don't show this if we already enabled the setting
guard !FilterListResourceDownloader.shared.isEnabled(for: FilterList.cookieConsentNoticesComponentID) else { return }
// Don't show this if we are presenting another popup already
guard !isOnboardingOrFullScreenCalloutPresented else { return }
// We only show the popup on second launch
guard !Preferences.General.isFirstLaunch.value else { return }
// Ensure we successfully shown basic onboarding first
guard Preferences.FullScreenCallout.omniboxCalloutCompleted.value else { return }

// Make sure we didn't already show this popup
guard presentedViewController == nil && FullScreenCalloutManager.shouldShowDefaultBrowserCallout(calloutType: .blockCookieConsentNotices) else {
return
}

let popover = PopoverController(
contentController: CookieNotificationBlockingConsentViewController(),
contentSizeBehavior: .preferredContentSize)
popover.addsConvenientDismissalMargins = false
popover.present(from: topToolbar.locationView.shieldsButton, on: self)
}
}

class CookieNotificationBlockingConsentViewController: UIHostingController<CookieNotificationBlockingConsentView>, PopoverContentComponent {
init() {
super.init(rootView: CookieNotificationBlockingConsentView())

self.preferredContentSize = CGSize(
width: CookieNotificationBlockingConsentView.contentWidth,
height: CookieNotificationBlockingConsentView.contentHeight
)

self.rootView.onDismiss = { [weak self] in
self?.dismiss(animated: true)
}
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.braveBackground
}
}
4 changes: 3 additions & 1 deletion Client/Frontend/Browser/FullScreenCalloutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import BraveShared
struct FullScreenCalloutManager {

enum FullScreenCalloutType {
case vpn, rewards, defaultBrowser
case vpn, rewards, defaultBrowser, blockCookieConsentNotices

/// The number of days passed to show certain type of callout
var period: Int {
switch self {
case .blockCookieConsentNotices: return 0
case .vpn: return 4
case .rewards: return 8
case .defaultBrowser: return 10
Expand All @@ -24,6 +25,7 @@ struct FullScreenCalloutManager {
/// The preference value stored for complete state
var preferenceValue: Preferences.Option<Bool> {
switch self {
case .blockCookieConsentNotices: return Preferences.FullScreenCallout.blockCookieConsentNoticesCalloutCompleted
case .vpn: return Preferences.FullScreenCallout.vpnCalloutCompleted
case .rewards: return Preferences.FullScreenCallout.rewardsCalloutCompleted
case .defaultBrowser: return Preferences.DefaultBrowserIntro.completed
Expand Down
4 changes: 4 additions & 0 deletions Client/Frontend/ClientPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ extension Preferences {
}

final public class FullScreenCallout {
/// Whether the block cookie consent notices callout is shown.
static let blockCookieConsentNoticesCalloutCompleted =
Option<Bool>(key: "fullScreenCallout.full-screen-cookie-consent-notices-callout-completed", default: false)

/// Whether the vpn callout is shown.
static let vpnCalloutCompleted =
Option<Bool>(key: "fullScreenCallout.full-screen-vpn-callout-completed", default: false)
Expand Down
115 changes: 115 additions & 0 deletions Client/Frontend/Popup/CookieNotificationBlockingConsentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2022 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import SwiftUI
import Strings
import DesignSystem
import BraveShared

struct CookieNotificationBlockingConsentView: View {
public static let contentHeight = 480.0
public static let contentWidth = 344.0
private static let gifHeight = 328.0
private static let bottomSectionHeight = contentHeight - gifHeight
private static let textPadding = 16.0

private let animation = Animation.easeOut(duration: 0.5).delay(0)
private let transition = AnyTransition.scale(scale: 1.1).combined(with: .opacity)

@Environment(\.presentationMode) @Binding private var presentationMode
@State private var showAnimation = false
var onDismiss: (() -> Void)?

private var yesButton: some View {
Button(Strings.yesBlockCookieConsentNotices) {
withAnimation(animation) {
self.showAnimation = true
}

if !FilterListResourceDownloader.shared.enableFilterList(for: FilterList.cookieConsentNoticesComponentID, isEnabled: true) {
assertionFailure("This filter list should exist or this UI is completely useless")
}

Task { @MainActor in
try await Task.sleep(seconds: 3.5)
self.dismiss()
}
}
.buttonStyle(BraveFilledButtonStyle(size: .large))
.multilineTextAlignment(.center)
.transition(transition)
}

private var noButton: some View {
Button(Strings.noThanks) {
self.dismiss()
}
.font(Font.body.weight(.semibold))
.foregroundColor(.accentColor)
.multilineTextAlignment(.center)
.transition(transition)
}

var body: some View {
ScrollView {
VStack {
VStack {
if !showAnimation {
VStack(spacing: Self.textPadding) {
Text(Strings.blockCookieConsentNoticesPopupTitle).font(.title)
Text(Strings.blockCookieConsentNoticesPopupDescription).font(.body)
}
.transition(transition)
.padding(Self.textPadding)
.padding(.top, 80)
.foregroundColor(Color(UIColor.braveLabel))
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)
}
}
.frame(width: Self.contentWidth)
.frame(minHeight: Self.gifHeight)
.background(
GIFImage(asset: "cookie-consent-animation", animate: showAnimation)
.frame(width: Self.contentWidth, height: Self.gifHeight, alignment: .top),
alignment: .top
)

VStack(spacing: Self.textPadding) {
if !showAnimation {
yesButton
noButton
}
}
.padding(Self.textPadding)
}
}
.frame(width: Self.contentWidth, height: Self.contentHeight)
.background(
Image("cookie-consent-background", bundle: .module),
alignment: .bottomLeading
)
.background(Color(UIColor.braveBackground))
}

private func dismiss() {
// Dismiss on presentation mode does not work on iOS 14
// when using the UIHostingController is parent view.
// As a workaround a completion handler is used instead.
if #available(iOS 15, *) {
presentationMode.dismiss()
} else {
onDismiss?()
}
}
}

#if DEBUG
struct CookieNotificationBlockingConsentView_Previews: PreviewProvider {
static var previews: some View {
CookieNotificationBlockingConsentView()
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,28 @@ import UIKit
import BraveUI
import BraveNews
import os.log
import Data
import Combine

class BraveShieldsAndPrivacySettingsController: TableViewController {
let profile: Profile
let tabManager: TabManager
let feedDataSource: FeedDataSource
let historyAPI: BraveHistoryAPI
private let cookieConsentNoticesRowUUID = UUID()
private var filterListsSubscription: AnyCancellable?
private var currentCookieConsentNoticeBlockingState: Bool

init(profile: Profile, tabManager: TabManager, feedDataSource: FeedDataSource, historyAPI: BraveHistoryAPI) {
self.profile = profile
self.tabManager = tabManager
self.feedDataSource = feedDataSource
self.historyAPI = historyAPI

self.currentCookieConsentNoticeBlockingState = FilterListResourceDownloader.shared.isEnabled(
for: FilterList.cookieConsentNoticesComponentID
)

super.init(style: .insetGrouped)
}

Expand All @@ -44,6 +54,32 @@ class BraveShieldsAndPrivacySettingsController: TableViewController {
manageWebsiteDataSection,
otherSettingsSection,
]

// Listen to changes on filter lists so we know we need to reload the section
filterListsSubscription = FilterListResourceDownloader.shared.$filterLists
.receive(on: DispatchQueue.main)
.sink { filterLists in
let filterList = filterLists.first(where: { $0.componentId == FilterList.cookieConsentNoticesComponentID })
let isEnabled = filterList?.isEnabled == true

if isEnabled != self.currentCookieConsentNoticeBlockingState {
self.currentCookieConsentNoticeBlockingState = isEnabled

guard let sectionIndex = self.dataSource.sections.firstIndex(where: { $0.uuid == self.shieldsSection.uuid }) else {
assertionFailure("Should exist")
return
}

guard let rowIndex = self.shieldsSection.rows.firstIndex(where: { $0.uuid == self.cookieConsentNoticesRowUUID.uuidString }) else {
assertionFailure("Should exist")
return
}

// Reload the section
self.shieldsSection.rows[rowIndex] = self.makeCookieConsentBlockingRow()
self.dataSource.sections[sectionIndex] = self.shieldsSection
}
}
}

// MARK: - Sections
Expand Down Expand Up @@ -108,7 +144,13 @@ class BraveShieldsAndPrivacySettingsController: TableViewController {
toggleCookieSetting(with: $0)
}
}),
.boolRow(title: Strings.fingerprintingProtection, detailText: Strings.fingerprintingProtectionDescription, option: Preferences.Shields.fingerprintingProtection),

.boolRow(
title: Strings.fingerprintingProtection,
detailText: Strings.fingerprintingProtectionDescription,
option: Preferences.Shields.fingerprintingProtection
),
makeCookieConsentBlockingRow(),
Row(
text: Strings.contentFiltering,
detailText: Strings.contentFilteringDescription,
Expand Down Expand Up @@ -252,6 +294,21 @@ class BraveShieldsAndPrivacySettingsController: TableViewController {
}()

// MARK: - Actions

private func makeCookieConsentBlockingRow() -> Row {
return .boolRow(
uuid: self.cookieConsentNoticesRowUUID,
title: Strings.blockCookieConsentNotices,
toggleValue: FilterListResourceDownloader.shared.isEnabled(
for: FilterList.cookieConsentNoticesComponentID
),
valueChange: { isEnabled in
if !FilterListResourceDownloader.shared.enableFilterList(for: FilterList.cookieConsentNoticesComponentID, isEnabled: isEnabled) {
assertionFailure("This filter list should exist or this UI is completely useless")
}
}, cellReuseId: "blockCookieConsentNoticesReuseIdentifier"
)
}

private func tappedClearPrivateData() {
let alertController = UIAlertController(
Expand Down
Loading

0 comments on commit df087be

Please sign in to comment.