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

feature(privacy): Add debug view to iOS to display blocked requests per-tab #26987

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
5 changes: 5 additions & 0 deletions components/brave_shields/core/common/features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions components/brave_shields/core/common/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if filterText.isEmpty would be easier to parse

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can drop @ViewBuilder since the computed value is wrapped in Group

Group {
Text(title)
Text(detail)
.font(.system(.caption, design: .monospaced))
.textSelection(.enabled)
}
.font(.body)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ struct ShieldsPanelView: View {
}

private let url: URL
private weak var tab: Tab?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably doesn't need to be weak since this is a View and ShieldsSettingsViewModel is already holding onto the Tab strongly

private let displayHost: String
@AppStorage("advancedShieldsExpanded") private var advancedShieldsExpanded = false
@ObservedObject private var viewModel: ShieldsSettingsViewModel
private var actionCallback: (Action) -> Void

@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 =
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ window.__firefox__.execute(function($) {

sendInfo.push({
resourceURL: resourceURL.href,
sourceURL: document.location.href,
sourceURL: $.windowOrigin,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intended?

Copy link
Collaborator Author

@StephenHeaps StephenHeaps Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this was missed in the local frames fix, just for our tracking protection stats. But this debug view feature piggy-backs on that script to catch requests blocked by the content blocker (as opposed to blocked by our request blocking scripts). Some additional info w/ examples.

stats

resourceType: resourceType
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -54,7 +81,7 @@ class ContentBlockerHelper {
}

var statsDidChange: ((TPPageStats) -> Void)?
var blockedRequests: Set<URL> = []
@Published var blockedRequests: OrderedSet<BlockedRequestInfo> = []

init(tab: Tab) {
self.tab = tab
Expand Down
69 changes: 69 additions & 0 deletions ios/brave-ios/Sources/BraveShields/ShieldStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,75 @@ extension Strings.Shields {
)
}

// MARK: - Adblock Debugging

extension Strings.Shields {
public static let blockedRequestsTitle = NSLocalizedString(
"blockedRequestsTitle",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to prefix these strings with Shields (e.g. Shields.blockedRequestTitle) for possible (though unlikely 😅) collision prevention

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 {
Expand Down
1 change: 1 addition & 0 deletions ios/browser/api/features/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions ios/browser/api/features/features.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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::
Expand Down
7 changes: 7 additions & 0 deletions ios/browser/flags/about_flags.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
Loading