From 742d6f35925040af89e1156b8aa8e5f95238eb44 Mon Sep 17 00:00:00 2001 From: Stephen Heaps Date: Tue, 10 Dec 2024 11:17:39 -0500 Subject: [PATCH 1/3] Use secure copy of window origin for tracking protection stats. --- .../Scripts_Dynamic/Scripts/Paged/TrackingProtectionStats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/Scripts/Paged/TrackingProtectionStats.js b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/Scripts/Paged/TrackingProtectionStats.js index c9b344fe1c31..06fa7af52cd7 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/Scripts/Paged/TrackingProtectionStats.js +++ b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/Scripts/Paged/TrackingProtectionStats.js @@ -31,7 +31,7 @@ window.__firefox__.execute(function($) { sendInfo.push({ resourceURL: resourceURL.href, - sourceURL: document.location.href, + sourceURL: $.windowOrigin, resourceType: resourceType }); From 16efa5aee459a2195b521d91395456c81e6bda41 Mon Sep 17 00:00:00 2001 From: Stephen Heaps Date: Wed, 11 Dec 2024 15:35:37 -0500 Subject: [PATCH 2/3] Add feature flag to display blocked requests per-tab in shields panel on iOS. --- .../brave_shields/core/common/features.cc | 5 ++ .../brave_shields/core/common/features.h | 1 + .../AdblockBlockedRequestsView.swift | 74 +++++++++++++++++++ .../Frontend/Shields/ShieldsPanelView.swift | 21 ++++++ .../Paged/ContentBlockerScriptHandler.swift | 14 +++- .../RequestBlockingContentScriptHandler.swift | 14 +++- .../ContentBlocker/ContentBlockerHelper.swift | 31 +++++++- .../Sources/BraveShields/ShieldStrings.swift | 69 +++++++++++++++++ ios/browser/api/features/features.h | 1 + ios/browser/api/features/features.mm | 5 ++ ios/browser/flags/about_flags.mm | 7 ++ 11 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdblockBlockedRequestsView.swift diff --git a/components/brave_shields/core/common/features.cc b/components/brave_shields/core/common/features.cc index 155a271f32d3..63bd9c0219f4 100644 --- a/components/brave_shields/core/common/features.cc +++ b/components/brave_shields/core/common/features.cc @@ -107,6 +107,11 @@ BASE_FEATURE(kBraveShredCacheData, #else base::FEATURE_DISABLED_BY_DEFAULT); #endif +// When enabled, will display debug menu for adblock features in the Shields +// panel. +BASE_FEATURE(kBraveIOSDebugAdblock, + "BraveIOSDebugAdblock", + base::FEATURE_DISABLED_BY_DEFAULT); // When enabled, show Strict (aggressive) fingerprinting mode in Brave Shields. BASE_FEATURE(kBraveShowStrictFingerprintingMode, "BraveShowStrictFingerprintingMode", diff --git a/components/brave_shields/core/common/features.h b/components/brave_shields/core/common/features.h index 0714290d1558..cc18e324df98 100644 --- a/components/brave_shields/core/common/features.h +++ b/components/brave_shields/core/common/features.h @@ -34,6 +34,7 @@ BASE_DECLARE_FEATURE(kBraveLocalhostAccessPermission); BASE_DECLARE_FEATURE(kBraveReduceLanguage); BASE_DECLARE_FEATURE(kBraveShredFeature); BASE_DECLARE_FEATURE(kBraveShredCacheData); +BASE_DECLARE_FEATURE(kBraveIOSDebugAdblock); BASE_DECLARE_FEATURE(kBraveShowStrictFingerprintingMode); BASE_DECLARE_FEATURE(kCosmeticFilteringExtraPerfMetrics); BASE_DECLARE_FEATURE(kCosmeticFilteringJsPerformance); diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdblockBlockedRequestsView.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdblockBlockedRequestsView.swift new file mode 100644 index 000000000000..5349ae5e1b87 --- /dev/null +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdblockBlockedRequestsView.swift @@ -0,0 +1,74 @@ +// Copyright 2024 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 https://mozilla.org/MPL/2.0/. + +import BraveShields +import Strings +import SwiftUI + +struct AdblockBlockedRequestsView: View { + + let url: String + @ObservedObject var contentBlockerHelper: ContentBlockerHelper + + @State private var filterText: String = "" + + private var blockedRequests: [BlockedRequestInfo] { + let blockedRequests = Array(contentBlockerHelper.blockedRequests) + guard !filterText.isEmpty else { + return blockedRequests + } + return blockedRequests.filter { + $0.requestURL.absoluteString.localizedCaseInsensitiveContains(filterText) + || $0.sourceURL.absoluteString.localizedCaseInsensitiveContains(filterText) + || $0.resourceType.rawValue.localizedCaseInsensitiveContains(filterText) + || $0.location.display.localizedCaseInsensitiveContains(filterText) + } + } + + public var body: some View { + List { + Section(header: Text(url)) { + ForEach(blockedRequests) { request in + VStack { + row( + title: String.localizedStringWithFormat("%@:", Strings.Shields.requestURLLabel), + detail: request.requestURL.absoluteString + ) + row( + title: String.localizedStringWithFormat("%@:", Strings.Shields.sourceURLLabel), + detail: request.sourceURL.absoluteString + ) + row( + title: String.localizedStringWithFormat("%@:", Strings.Shields.resourceTypeLabel), + detail: request.resourceType.rawValue + ) + row( + title: String.localizedStringWithFormat("%@:", Strings.Shields.aggressiveLabel), + detail: "\(request.isAggressive)" + ) + row( + title: String.localizedStringWithFormat("%@:", Strings.Shields.blockedByLabel), + detail: request.location.display + ) + } + } + } + } + .navigationTitle(Strings.Shields.blockedRequestsTitle) + .toolbar(.visible) + .searchable(text: $filterText) + } + + @ViewBuilder private func row(title: String, detail: String) -> some View { + Group { + Text(title) + Text(detail) + .font(.system(.caption, design: .monospaced)) + .textSelection(.enabled) + } + .font(.body) + .frame(maxWidth: .infinity, alignment: .leading) + } +} diff --git a/ios/brave-ios/Sources/Brave/Frontend/Shields/ShieldsPanelView.swift b/ios/brave-ios/Sources/Brave/Frontend/Shields/ShieldsPanelView.swift index 83faf1088c20..060582202342 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Shields/ShieldsPanelView.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Shields/ShieldsPanelView.swift @@ -27,6 +27,7 @@ struct ShieldsPanelView: View { } private let url: URL + private weak var tab: Tab? private let displayHost: String @AppStorage("advancedShieldsExpanded") private var advancedShieldsExpanded = false @ObservedObject private var viewModel: ShieldsSettingsViewModel @@ -34,6 +35,7 @@ struct ShieldsPanelView: View { @MainActor init(url: URL, tab: Tab, domain: Domain, callback: @escaping (Action) -> Void) { self.url = url + self.tab = tab self.viewModel = ShieldsSettingsViewModel(tab: tab, domain: domain) self.actionCallback = callback self.displayHost = @@ -251,6 +253,25 @@ struct ShieldsPanelView: View { .padding(.vertical, 4) } } + if FeatureList.kBraveIOSDebugAdblock.enabled, let contentBlocker = tab?.contentBlocker { + ShieldSettingRow { + NavigationLink { + AdblockBlockedRequestsView( + url: url.baseDomain ?? url.absoluteDisplayString, + contentBlockerHelper: contentBlocker + ) + } label: { + ShieldSettingsNavigationWrapper { + Text(Strings.Shields.blockedRequestsTitle) + .frame(maxWidth: .infinity, alignment: .leading) + .multilineTextAlignment(.leading) + } + } + .foregroundStyle(Color(.bravePrimary)) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 4) + } + } } @ViewBuilder private var globalSettingsSectionView: some View { diff --git a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/ContentBlockerScriptHandler.swift b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/ContentBlockerScriptHandler.swift index 95162142be31..c4f4006dfb47 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/ContentBlockerScriptHandler.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/ContentBlockerScriptHandler.swift @@ -136,8 +136,18 @@ extension ContentBlockerHelper: TabContentScript { } // First check to make sure we're not counting the same repetitive requests multiple times - guard !self.blockedRequests.contains(requestURL) else { return } - self.blockedRequests.insert(requestURL) + guard !self.blockedRequests.contains(where: { $0.requestURL == requestURL }) else { + return + } + self.blockedRequests.append( + .init( + requestURL: requestURL, + sourceURL: sourceURL, + resourceType: dto.resourceType, + isAggressive: domain.globalBlockAdsAndTrackingLevel.isAggressive, + location: .contentBlocker + ) + ) // Increase global stats (here due to BlocklistName being in Client and BraveGlobalShieldStats being // in BraveShared) diff --git a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/RequestBlockingContentScriptHandler.swift b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/RequestBlockingContentScriptHandler.swift index c62caf930e2d..c017f21713e0 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/RequestBlockingContentScriptHandler.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Paged/RequestBlockingContentScriptHandler.swift @@ -109,11 +109,21 @@ class RequestBlockingContentScriptHandler: TabContentScript { ) } - if shouldBlock && !tab.contentBlocker.blockedRequests.contains(requestURL) { + if shouldBlock + && !tab.contentBlocker.blockedRequests.contains(where: { $0.requestURL == requestURL }) + { BraveGlobalShieldStats.shared.adblock += 1 let stats = tab.contentBlocker.stats tab.contentBlocker.stats = stats.adding(adCount: 1) - tab.contentBlocker.blockedRequests.insert(requestURL) + tab.contentBlocker.blockedRequests.append( + .init( + requestURL: requestURL, + sourceURL: windowOriginURL, + resourceType: dto.data.resourceType, + isAggressive: domain.globalBlockAdsAndTrackingLevel.isAggressive, + location: .requestBlocking + ) + ) } replyHandler(shouldBlock, nil) diff --git a/ios/brave-ios/Sources/Brave/WebFilters/ContentBlocker/ContentBlockerHelper.swift b/ios/brave-ios/Sources/Brave/WebFilters/ContentBlocker/ContentBlockerHelper.swift index f67f01c29b2d..28004037ac8f 100644 --- a/ios/brave-ios/Sources/Brave/WebFilters/ContentBlocker/ContentBlockerHelper.swift +++ b/ios/brave-ios/Sources/Brave/WebFilters/ContentBlocker/ContentBlockerHelper.swift @@ -2,7 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +import BraveCore import BraveShared +import Collections import Combine import Data import Shared @@ -37,7 +39,32 @@ enum BlockingStrength: String { static let allOptions: [BlockingStrength] = [.basic, .strict] } -class ContentBlockerHelper { +struct BlockedRequestInfo: Hashable, Identifiable { + enum Location: String { + case contentBlocker + case requestBlocking + + var display: String { + switch self { + case .contentBlocker: + return Strings.Shields.contentBlocker + case .requestBlocking: + return Strings.Shields.requestBlocking + } + } + } + let requestURL: URL + let sourceURL: URL + let resourceType: AdblockEngine.ResourceType + let isAggressive: Bool + let location: Location + + var id: String { + "\(requestURL)\(sourceURL)\(resourceType.rawValue)\(isAggressive)\(location.rawValue)" + } +} + +class ContentBlockerHelper: ObservableObject { private(set) weak var tab: Tab? /// The rule lists that are loaded into the current tab @@ -54,7 +81,7 @@ class ContentBlockerHelper { } var statsDidChange: ((TPPageStats) -> Void)? - var blockedRequests: Set = [] + @Published var blockedRequests: OrderedSet = [] init(tab: Tab) { self.tab = tab diff --git a/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift b/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift index c32edf165088..e6210432e911 100644 --- a/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift +++ b/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift @@ -113,6 +113,75 @@ extension Strings.Shields { ) } +// MARK: - Adblock Debugging + +extension Strings.Shields { + public static let blockedRequestsTitle = NSLocalizedString( + "blockedRequestsTitle", + tableName: "BraveShared", + bundle: .module, + value: "Blocked Requests", + comment: + "The title displayed in the navigation bar of Blocked Requests view." + ) + public static let requestURLLabel = NSLocalizedString( + "requestURLLabel", + tableName: "BraveShared", + bundle: .module, + value: "Request URL", + comment: + "A label displayed above the request url that was blocked in Blocked Requests view." + ) + public static let sourceURLLabel = NSLocalizedString( + "sourceURLLabel", + tableName: "BraveShared", + bundle: .module, + value: "Source URL", + comment: + "A label displayed above the source url of a request that was blocked in Blocked Requests view." + ) + public static let resourceTypeLabel = NSLocalizedString( + "resourceTypeLabel", + tableName: "BraveShared", + bundle: .module, + value: "Resource Type", + comment: + "A label displayed above the resource type of a request that was blocked in Blocked Requests view." + ) + public static let aggressiveLabel = NSLocalizedString( + "aggressiveLabel", + tableName: "BraveShared", + bundle: .module, + value: "Aggressive", + comment: + "A label displayed above a value indicating if the site is in aggressive mode in Blocked Requests view." + ) + public static let blockedByLabel = NSLocalizedString( + "blockedByLabel", + tableName: "BraveShared", + bundle: .module, + value: "Blocked By", + comment: + "A label displayed above the location a request was blocked in Blocked Requests view." + ) + public static let contentBlocker = NSLocalizedString( + "contentBlocker", + tableName: "BraveShared", + bundle: .module, + value: "Content Blocker", + comment: + "Used to describe when a request was blocked by the Content Blocker in Blocked Requests view." + ) + public static let requestBlocking = NSLocalizedString( + "requestBlocking", + tableName: "BraveShared", + bundle: .module, + value: "Request Blocking", + comment: + "Used to describe when a request was blocked by our request blocking scripts in Blocked Requests view." + ) +} + // MARK: - Anti Ad-Block Warning extension Strings.Shields { diff --git a/ios/browser/api/features/features.h b/ios/browser/api/features/features.h index 07d3451c9497..e2c7f494d714 100644 --- a/ios/browser/api/features/features.h +++ b/ios/browser/api/features/features.h @@ -52,6 +52,7 @@ OBJC_EXPORT @property(class, nonatomic, readonly) Feature* kBraveSearchDefaultAPIFeature; @property(class, nonatomic, readonly) Feature* kBraveShredFeature; @property(class, nonatomic, readonly) Feature* kBraveShredCacheData; +@property(class, nonatomic, readonly) Feature* kBraveIOSDebugAdblock; @property(class, nonatomic, readonly) Feature* kBraveShowStrictFingerprintingMode; @property(class, nonatomic, readonly) Feature* kBraveSync; diff --git a/ios/browser/api/features/features.mm b/ios/browser/api/features/features.mm index 4633e57ee0f3..4c646d0f57fd 100644 --- a/ios/browser/api/features/features.mm +++ b/ios/browser/api/features/features.mm @@ -206,6 +206,11 @@ + (Feature*)kBraveShredCacheData { initWithFeature:&brave_shields::features::kBraveShredCacheData]; } ++ (Feature*)kBraveIOSDebugAdblock { + return [[Feature alloc] + initWithFeature:&brave_shields::features::kBraveIOSDebugAdblock]; +} + + (Feature*)kBraveShowStrictFingerprintingMode { return [[Feature alloc] initWithFeature:&brave_shields::features:: diff --git a/ios/browser/flags/about_flags.mm b/ios/browser/flags/about_flags.mm index 126aa83a44ed..ee8e6688e509 100644 --- a/ios/browser/flags/about_flags.mm +++ b/ios/browser/flags/about_flags.mm @@ -113,6 +113,13 @@ flags_ui::kOsIos, \ FEATURE_VALUE_TYPE( \ security_interstitials::features::kHttpsOnlyMode), \ + }, \ + { \ + "ios-debug-adblock", \ + "Enable Debug Adblock views", \ + "Enable debug view for adblock features in Shields panel", \ + flags_ui::kOsIos, \ + FEATURE_VALUE_TYPE(brave_shields::features::kBraveIOSDebugAdblock), \ }) #define BRAVE_AI_CHAT_FEATURE_ENTRIES \ From 84ff08e40d9cd0e9f39c3855cf5e049d95896075 Mon Sep 17 00:00:00 2001 From: Stephen Heaps Date: Wed, 11 Dec 2024 16:51:43 -0500 Subject: [PATCH 3/3] review(kylehickinson): prefix new shield strings keys. --- .../AdblockBlockedRequestsView.swift | 4 ++-- .../Frontend/Shields/ShieldsPanelView.swift | 2 +- .../Sources/BraveShields/ShieldStrings.swift | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdblockBlockedRequestsView.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdblockBlockedRequestsView.swift index 5349ae5e1b87..9761afdf3b64 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdblockBlockedRequestsView.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Features/ShieldsPrivacy/AdblockBlockedRequestsView.swift @@ -16,7 +16,7 @@ struct AdblockBlockedRequestsView: View { private var blockedRequests: [BlockedRequestInfo] { let blockedRequests = Array(contentBlockerHelper.blockedRequests) - guard !filterText.isEmpty else { + if filterText.isEmpty { return blockedRequests } return blockedRequests.filter { @@ -61,7 +61,7 @@ struct AdblockBlockedRequestsView: View { .searchable(text: $filterText) } - @ViewBuilder private func row(title: String, detail: String) -> some View { + private func row(title: String, detail: String) -> some View { Group { Text(title) Text(detail) diff --git a/ios/brave-ios/Sources/Brave/Frontend/Shields/ShieldsPanelView.swift b/ios/brave-ios/Sources/Brave/Frontend/Shields/ShieldsPanelView.swift index 060582202342..41f0fcf463db 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Shields/ShieldsPanelView.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Shields/ShieldsPanelView.swift @@ -27,7 +27,7 @@ struct ShieldsPanelView: View { } private let url: URL - private weak var tab: Tab? + private var tab: Tab? private let displayHost: String @AppStorage("advancedShieldsExpanded") private var advancedShieldsExpanded = false @ObservedObject private var viewModel: ShieldsSettingsViewModel diff --git a/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift b/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift index e6210432e911..48a929cd5033 100644 --- a/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift +++ b/ios/brave-ios/Sources/BraveShields/ShieldStrings.swift @@ -117,7 +117,7 @@ extension Strings.Shields { extension Strings.Shields { public static let blockedRequestsTitle = NSLocalizedString( - "blockedRequestsTitle", + "shields.blockedRequestsTitle", tableName: "BraveShared", bundle: .module, value: "Blocked Requests", @@ -125,7 +125,7 @@ extension Strings.Shields { "The title displayed in the navigation bar of Blocked Requests view." ) public static let requestURLLabel = NSLocalizedString( - "requestURLLabel", + "shields.requestURLLabel", tableName: "BraveShared", bundle: .module, value: "Request URL", @@ -133,7 +133,7 @@ extension Strings.Shields { "A label displayed above the request url that was blocked in Blocked Requests view." ) public static let sourceURLLabel = NSLocalizedString( - "sourceURLLabel", + "shields.sourceURLLabel", tableName: "BraveShared", bundle: .module, value: "Source URL", @@ -141,7 +141,7 @@ extension Strings.Shields { "A label displayed above the source url of a request that was blocked in Blocked Requests view." ) public static let resourceTypeLabel = NSLocalizedString( - "resourceTypeLabel", + "shields.resourceTypeLabel", tableName: "BraveShared", bundle: .module, value: "Resource Type", @@ -149,7 +149,7 @@ extension Strings.Shields { "A label displayed above the resource type of a request that was blocked in Blocked Requests view." ) public static let aggressiveLabel = NSLocalizedString( - "aggressiveLabel", + "shields.aggressiveLabel", tableName: "BraveShared", bundle: .module, value: "Aggressive", @@ -157,7 +157,7 @@ extension Strings.Shields { "A label displayed above a value indicating if the site is in aggressive mode in Blocked Requests view." ) public static let blockedByLabel = NSLocalizedString( - "blockedByLabel", + "shields.blockedByLabel", tableName: "BraveShared", bundle: .module, value: "Blocked By", @@ -165,7 +165,7 @@ extension Strings.Shields { "A label displayed above the location a request was blocked in Blocked Requests view." ) public static let contentBlocker = NSLocalizedString( - "contentBlocker", + "shields.contentBlocker", tableName: "BraveShared", bundle: .module, value: "Content Blocker", @@ -173,7 +173,7 @@ extension Strings.Shields { "Used to describe when a request was blocked by the Content Blocker in Blocked Requests view." ) public static let requestBlocking = NSLocalizedString( - "requestBlocking", + "shields.requestBlocking", tableName: "BraveShared", bundle: .module, value: "Request Blocking",