Skip to content

Commit

Permalink
[navigation #5] content blocking scripts, click to load TabExtensions (
Browse files Browse the repository at this point in the history
…#888)

<!--
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.
-->

Task/Issue URL: https://app.asana.com/0/0/1203487090719126/f
Tech Design URL:
https://app.asana.com/0/481882893211075/1203268245242140/f
CC: @tomasstrba @bwaresiak 

**Description**:
FBProtection(click to load) and ContentBlockingUserScripts/surrogates
handling moved to Tab Extensions

<!--
Tagging instructions
If this PR isn't ready to be merged for whatever reason it should be
marked with the `DO NOT MERGE` label (particularly if it's a draft)
If it's pending Product Review/PFR, please add the `Pending Product
Review` label.

If at any point it isn't actively being worked on/ready for
review/otherwise moving forward (besides the above PR/PFR exception)
strongly consider closing it (or not opening it in the first place). If
you decide not to close it, make sure it's labelled to make it clear the
PRs state and comment with more information.
-->

**Steps to test this PR**:
1. Validate Click To Load works
https://privacy-test-pages.glitch.me/privacy-protections/click-to-load/
2. Validate Privacy Dashboard is updated with regular, 3rd party and
surrogate blocked trackers
3. Ensure awaitContentBlockingAssetsInstalled is called on non-ddg
navigations
  • Loading branch information
mallexxx authored Mar 20, 2023
1 parent 6fbc58d commit b3db06d
Show file tree
Hide file tree
Showing 96 changed files with 4,259 additions and 2,025 deletions.
292 changes: 206 additions & 86 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions DuckDuckGo/Autoconsent/AutoconsentUserScript.swift
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,7 @@ extension AutoconsentUserScript {
return
}

if !url.isHttp && !url.isHttps
// bundled test page is served from file://
&& !(NSApp.isRunningIntegrationTests && (url.path.hasSuffix("/autoconsent-test-page.html") || url.path.hasSuffix("/autoconsent-test-page-banner.html"))) {

guard [.http, .https].contains(url.navigationalScheme) else {
// ignore special schemes
os_log("Ignoring special URL scheme: %s", log: .autoconsent, type: .debug, messageData.url)
replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection
Expand Down
7 changes: 4 additions & 3 deletions DuckDuckGo/Autoconsent/UI/CookieConsentPopoverManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ final class CookieConsentPopoverManager: CookieConsentPopoverDelegate {
private(set) var popOver: CookieConsentPopover?

func cookieConsentPopover(_ popOver: CookieConsentPopover, didFinishWithResult result: Bool) {
popOver.close(animated: true) { [weak self] in
self?.popOver = nil
self?.currentTab = nil
popOver.close(animated: true) {
withExtendedLifetime(popOver) {}
}
self.popOver = nil
self.currentTab = nil

if let completion = completion {
completion(result)
Expand Down
4 changes: 2 additions & 2 deletions DuckDuckGo/Bookmarks/Model/Bookmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ final class Bookmark: BaseBookmarkEntity {

let faviconManagement: FaviconManagement
func favicon(_ sizeCategory: Favicon.SizeCategory) -> NSImage? {
if let privatePlayerFavicon = PrivatePlayer.shared.image(for: self) {
return privatePlayerFavicon
if let duckPlayerFavicon = DuckPlayer.shared.image(for: self) {
return duckPlayerFavicon
}

if let url = urlObject {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ extension NavigationAction {

extension CustomNavigationType {
static let userEnteredUrl = CustomNavigationType(rawValue: "userEnteredUrl")
static let tabContentUpdate = CustomNavigationType(rawValue: "tabContentUpdate")
}
5 changes: 5 additions & 0 deletions DuckDuckGo/Common/Extensions/URLExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ extension URL {
return Self.preferences.appendingPathComponent(pane.rawValue)
}

var isHypertextURL: Bool {
guard let scheme = self.scheme.map(NavigationalScheme.init(rawValue:)) else { return false }
return NavigationalScheme.validSchemes.contains(scheme)
}

// MARK: Pixel

static let pixelBase = ProcessInfo.processInfo.environment["PIXEL_BASE_URL", default: "https://improving.duckduckgo.com"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ extension WKWebViewConfiguration {
preferences.javaScriptCanOpenWindowsAutomatically = true
preferences.isFraudulentWebsiteWarningEnabled = false

if urlSchemeHandler(forURLScheme: PrivatePlayer.privatePlayerScheme) == nil {
setURLSchemeHandler(PrivatePlayerSchemeHandler(), forURLScheme: PrivatePlayer.privatePlayerScheme)
if urlSchemeHandler(forURLScheme: DuckPlayer.duckPlayerScheme) == nil {
setURLSchemeHandler(DuckPlayerSchemeHandler(), forURLScheme: DuckPlayer.duckPlayerScheme)
}

let userContentController = UserContentController(assetsPublisher: contentBlocking.contentBlockingAssetsPublisher,
Expand Down
5 changes: 5 additions & 0 deletions DuckDuckGo/Common/Extensions/WKWebViewExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ extension WKWebView {
return self.value(forKey: Selector.fullScreenPlaceholderView) as? NSView
}

func removeFocusFromWebView() {
guard self.window?.firstResponder === self else { return }
self.superview?.makeMeFirstResponder()
}

private enum Selector {
static let fullScreenPlaceholderView = "_fullScreenPlaceholderView"
static let printOperationWithPrintInfoForFrame = "_printOperationWithPrintInfo:forFrame:"
Expand Down
26 changes: 26 additions & 0 deletions DuckDuckGo/Common/FileSystem/WorkspaceProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// WorkspaceProtocol.swift
//
// Copyright © 2023 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

protocol Workspace {
func urlForApplication(toOpen url: URL) -> URL?
@discardableResult
func open(_ url: URL) -> Bool
}
extension NSWorkspace: Workspace {}
16 changes: 10 additions & 6 deletions DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@ struct UserText {
static let autoconsentCheckboxTitle = NSLocalizedString("autoconsent.checkbox.title", value: "Let DuckDuckGo manage cookie consent pop-ups", comment: "Autoconsent settings checkbox title")
static let autoconsentExplanation = NSLocalizedString("autoconsent.explanation", value: "When DuckDuckGo detects cookie consent pop-ups on sites you visit, we can try to automatically set your cookie preferences to minimize cookies and maximize privacy, then close the pop-ups. Some sites don't provide an option to manage cookie preferences, so we can only hide pop-ups like these.", comment: "Autoconsent feature explanation in settings")

static let privatePlayerSettingsTitle = NSLocalizedString("private-player.title", value: "Duck Player", comment: "Private YouTube Player settings title")
static let privatePlayerAlwaysOpenInPlayer = NSLocalizedString("private-player.always-open-in-player", value: "Always open YouTube videos in Duck Player", comment: "Private YouTube Player option")
static let privatePlayerShowPlayerButtons = NSLocalizedString("private-player.show-buttons", value: "Show option to use Duck Player over YouTube previews on hover", comment: "Private YouTube Player option")
static let privatePlayerOff = NSLocalizedString("private-player.off", value: "Never use Duck Player", comment: "Private YouTube Player option")
static let privatePlayerExplanation = NSLocalizedString("private-player.explanation", value: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations.", comment: "Private YouTube Player explanation in settings")
static let duckPlayerSettingsTitle = NSLocalizedString("duck-player.title", value: "Duck Player", comment: "Private YouTube Player settings title")
static let duckPlayerAlwaysOpenInPlayer = NSLocalizedString("duck-player.always-open-in-player", value: "Always open YouTube videos in Duck Player", comment: "Private YouTube Player option")
static let duckPlayerShowPlayerButtons = NSLocalizedString("duck-player.show-buttons", value: "Show option to use Duck Player over YouTube previews on hover", comment: "Private YouTube Player option")
static let duckPlayerOff = NSLocalizedString("duck-player.off", value: "Never use Duck Player", comment: "Private YouTube Player option")
static let duckPlayerExplanation = NSLocalizedString("duck-player.explanation", value: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations.", comment: "Private YouTube Player explanation in settings")

static let gpcSettingsTitle = NSLocalizedString("gpc.title", value: "Global Privacy Control (GPC)", comment: "GPC settings title")
static let gpcCheckboxTitle = NSLocalizedString("gpc.checkbox.title", value: "Enable Global Privacy Control", comment: "GPC settings checkbox title")
Expand Down Expand Up @@ -234,6 +234,10 @@ struct UserText {
static let externalSchemePermissionAuthorizationFormat = NSLocalizedString("permission.authorization.externalScheme.format",
value: "“%@” would like to open this link in %@",
comment: "Popover asking for domain %@ to open link in External App (%@)")
static let externalSchemePermissionAuthorizationNoDomainFormat = NSLocalizedString("permission.authorization.externalScheme.empty.format",
value: "Open this link in %@?",
comment: "Popover asking to open link in External App (%@)")
static let permissionAlwaysAllowOnDomainCheckbox = NSLocalizedString("dashboard.permission.allow.on", value: "Always allow on", comment: "Permission Popover 'Always allow on' (for domainName) checkbox")

static let permissionMicrophone = NSLocalizedString("permission.microphone", value: "Microphone", comment: "Microphone input media device name")
static let permissionCamera = NSLocalizedString("permission.camera", value: "Camera", comment: "Camera input media device name")
Expand Down Expand Up @@ -271,7 +275,7 @@ struct UserText {
static let defaultBrowser = NSLocalizedString("preferences.default-browser", value: "Default Browser", comment: "Show default browser preferences")
static let appearance = NSLocalizedString("preferences.appearance", value: "Appearance", comment: "Show appearance preferences")
static let privacy = NSLocalizedString("preferences.privacy", value: "Privacy", comment: "Show privacy browser preferences")
static let privatePlayer = NSLocalizedString("preferences.private-player", value: "Duck Player", comment: "Show Duck Player browser preferences")
static let duckPlayer = NSLocalizedString("preferences.duck-player", value: "Duck Player", comment: "Show Duck Player browser preferences")
static let about = NSLocalizedString("preferences.about", value: "About", comment: "Show about screen")

static let downloads = NSLocalizedString("preferences.downloads", value: "Downloads", comment: "Show downloads browser preferences")
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public struct UserDefaultsWrapper<T> {
case lastUsedCustomDownloadLocation = "preferences.custom-last-used-download-location"
case alwaysRequestDownloadLocationKey = "preferences.download-location.always-request"
case autoconsentEnabled = "preferences.autoconsent-enabled"
case privatePlayerMode = "preferences.duck-player"
case duckPlayerMode = "preferences.duck-player"
case youtubeOverlayInteracted = "preferences.youtube-overlay-interacted"

case selectedPasswordManager = "preferences.autofill.selected-password-manager"
Expand Down
4 changes: 2 additions & 2 deletions DuckDuckGo/Common/View/SwiftUI/FaviconView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ struct FaviconView: View {
}

func refreshImage() {
if let privatePlayerImage = PrivatePlayer.shared.image(for: self) {
image = privatePlayerImage
if let duckPlayerImage = DuckPlayer.shared.image(for: self) {
image = duckPlayerImage
return
}

Expand Down
11 changes: 11 additions & 0 deletions DuckDuckGo/FileDownload/Model/FileDownloadError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ enum FileDownloadError: Error {
case failedToCompleteDownloadTask(underlyingError: Error?, resumeData: Data?, isRetryable: Bool)
}

extension FileDownloadError: LocalizedError {
var errorDescription: String? {
switch self {
case .failedToMoveFileToDownloads:
return "FileDownloadError: failedToMoveFileToDownloads"
case .failedToCompleteDownloadTask(underlyingError: let error, resumeData: let data, isRetryable: let isRetryable):
return "FileDownloadError(\(isRetryable ? "retryable\(data != nil ? "+resumeData" : "")" : "non-retryable")) underlyingError: \(error.debugDescription)"
}
}
}

extension FileDownloadError {

var underlyingError: Error? {
Expand Down
4 changes: 2 additions & 2 deletions DuckDuckGo/History/ViewModel/VisitViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ final class VisitViewModel {
return nil
}

if historyEntry.url.isPrivatePlayer {
return .privatePlayer
if historyEntry.url.isDuckPlayer {
return .duckPlayer
}

return faviconManager.getCachedFavicon(for: historyEntry.url, sizeCategory: .small)?.image
Expand Down
10 changes: 5 additions & 5 deletions DuckDuckGo/HomePage/Model/HomePageRecentlyVisitedModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ final class RecentlyVisitedSiteModel: ObservableObject {

private let baseURL: URL?
private let domainPlaceholder: String?
private let privatePlayer: PrivatePlayer
private let duckPlayer: DuckPlayer

@Published var isFavorite: Bool
@Published var isFireproof: Bool
Expand All @@ -187,15 +187,15 @@ final class RecentlyVisitedSiteModel: ObservableObject {
init?(originalURL: URL,
bookmarkManager: BookmarkManager = LocalBookmarkManager.shared,
fireproofDomains: FireproofDomains = FireproofDomains.shared,
privatePlayer: PrivatePlayer = PrivatePlayer.shared) {
duckPlayer: DuckPlayer = DuckPlayer.shared) {
guard let domain = originalURL.host?.droppingWwwPrefix() else {
return nil
}

self.privatePlayer = privatePlayer
self.duckPlayer = duckPlayer

self.domain = domain
self.domainPlaceholder = privatePlayer.domainForRecentlyVisitedSite(with: originalURL)
self.domainPlaceholder = duckPlayer.domainForRecentlyVisitedSite(with: originalURL)

var components = URLComponents()
components.scheme = originalURL.scheme
Expand Down Expand Up @@ -245,7 +245,7 @@ final class RecentlyVisitedSiteModel: ObservableObject {
urlsToRemove.append($0.url)
}

} else if let displayTitle = privatePlayer.title(for: $0) {
} else if let displayTitle = duckPlayer.title(for: $0) {

$0.displayTitle = displayTitle

Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Main/View/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ final class MainViewController: NSViewController {
switch selectedTabViewModel.tab.content {
case .homePage, .onboarding:
navigationBarViewController.addressBarViewController?.addressBarTextField.makeMeFirstResponder()
case .url, .privatePlayer:
case .url:
browserTabViewController.makeWebViewFirstResponder()
case .preferences:
browserTabViewController.preferencesViewController?.view.makeMeFirstResponder()
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Menus/MainMenu.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ CQ
<menuItem title="Reset Youtube Overlay Interactions" id="tTd-ZO-0Zx">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="resetPrivatePlayerOverlayInteractions:" target="Ady-hI-5gd" id="Tr1-yK-ayF"/>
<action selector="resetDuckPlayerOverlayInteractions:" target="Ady-hI-5gd" id="Tr1-yK-ayF"/>
</connections>
</menuItem>
</items>
Expand Down
12 changes: 6 additions & 6 deletions DuckDuckGo/Menus/MainMenuActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,13 @@ extension MainViewController {
// MARK: - Main Menu

@IBAction func openPreferences(_ sender: Any?) {
browserTabViewController.openNewTab(with: .anyPreferencePane, selected: true)
browserTabViewController.openNewTab(with: .anyPreferencePane)
}

// MARK: - File

@IBAction func newTab(_ sender: Any?) {
browserTabViewController.openNewTab(with: .homePage, selected: true)
browserTabViewController.openNewTab(with: .homePage)
}

@IBAction func openLocation(_ sender: Any?) {
Expand Down Expand Up @@ -377,7 +377,7 @@ extension MainViewController {

@IBAction func home(_ sender: Any?) {
guard view.window?.isPopUpWindow == false else {
browserTabViewController.openNewTab(with: .homePage, selected: true)
browserTabViewController.openNewTab(with: .homePage)
return
}
guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else {
Expand Down Expand Up @@ -480,7 +480,7 @@ extension MainViewController {
}

@IBAction func showManageBookmarks(_ sender: Any?) {
browserTabViewController.openNewTab(with: .bookmarks, selected: true)
browserTabViewController.openNewTab(with: .bookmarks)
}

// MARK: - Window
Expand Down Expand Up @@ -645,8 +645,8 @@ extension MainViewController {
tabCollectionViewModel.pinnedTabsManager?.tabCollection.removeAll()
}

@IBAction func resetPrivatePlayerOverlayInteractions(_ sender: Any?) {
PrivatePlayerPreferences.shared.youtubeOverlayInteracted = false
@IBAction func resetDuckPlayerOverlayInteractions(_ sender: Any?) {
DuckPlayerPreferences.shared.youtubeOverlayInteracted = false
}

@IBAction func showSaveCredentialsPopover(_ sender: Any?) {
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Menus/SharingMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ final class SharingMenu: NSMenu {
return
}

let sharingData = PrivatePlayer.shared.sharingData(for: tabViewModel.title, url: url) ?? (tabViewModel.title, url)
let sharingData = DuckPlayer.shared.sharingData(for: tabViewModel.title, url: url) ?? (tabViewModel.title, url)
service.subject = sharingData.title
service.perform(withItems: [sharingData.url])
}
Expand Down
28 changes: 10 additions & 18 deletions DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ final class AddressBarButtonsViewController: NSViewController {
privacyEntryPointButton.state = .on

privacyInfoCancellable?.cancel()
privacyInfoCancellable = selectedTabViewModel.tab.$privacyInfo
privacyInfoCancellable = selectedTabViewModel.tab.privacyInfoPublisher
.dropFirst()
.receive(on: DispatchQueue.main)
.sink { [weak privacyDashboardPopover, weak selectedTabViewModel] _ in
Expand Down Expand Up @@ -652,23 +652,17 @@ final class AddressBarButtonsViewController: NSViewController {

guard let selectedTabViewModel = tabCollectionViewModel.selectedTabViewModel else { return }

if controllerMode == .editing(isUrl: false) {
[geolocationButton, cameraButton, microphoneButton, popupsButton, externalSchemeButton].forEach {
$0?.buttonState = .none
}
} else {
geolocationButton.buttonState = selectedTabViewModel.usedPermissions.geolocation
geolocationButton.buttonState = selectedTabViewModel.usedPermissions.geolocation

let (camera, microphone) = PermissionState?.combineCamera(selectedTabViewModel.usedPermissions.camera,
withMicrophone: selectedTabViewModel.usedPermissions.microphone)
cameraButton.buttonState = camera
microphoneButton.buttonState = microphone
let (camera, microphone) = PermissionState?.combineCamera(selectedTabViewModel.usedPermissions.camera,
withMicrophone: selectedTabViewModel.usedPermissions.microphone)
cameraButton.buttonState = camera
microphoneButton.buttonState = microphone

popupsButton.buttonState = selectedTabViewModel.usedPermissions.popups?.isRequested == true // show only when there're popups blocked
? selectedTabViewModel.usedPermissions.popups
: nil
externalSchemeButton.buttonState = selectedTabViewModel.usedPermissions.externalScheme
}
popupsButton.buttonState = selectedTabViewModel.usedPermissions.popups?.isRequested == true // show only when there're popups blocked
? selectedTabViewModel.usedPermissions.popups
: nil
externalSchemeButton.buttonState = selectedTabViewModel.usedPermissions.externalScheme
}

private func showOrHidePermissionPopoverIfNeeded() {
Expand Down Expand Up @@ -767,8 +761,6 @@ final class AddressBarButtonsViewController: NSViewController {

let isNotSecure = url.scheme == URL.NavigationalScheme.http.rawValue

let parentEntity = ContentBlocking.shared.trackerDataManager.trackerData.findEntity(forHost: host)

let configuration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig
let isUnprotected = configuration.isUserUnprotected(domain: host)

Expand Down
Loading

0 comments on commit b3db06d

Please sign in to comment.