diff --git a/Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html b/Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html index b75fc84dc3e..b79155175a4 100644 --- a/Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html +++ b/Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html @@ -20,6 +20,29 @@

%blocked_title%

%blocked_subtitle%

%blocked_domain%

%blocked_description%

+
+ + +
+ + diff --git a/Sources/Brave/Assets/Interstitial Pages/Scripts/BlockedDomain.js b/Sources/Brave/Assets/Interstitial Pages/Scripts/BlockedDomain.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css b/Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css index 21fc3cdc934..b00c2c7f892 100644 --- a/Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css +++ b/Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css @@ -10,20 +10,20 @@ html { } .post { - padding-top: max(25px, env(safe-area-inset-top)); - padding-bottom: max(25px, env(safe-area-inset-bottom)); - padding-left: max(25px, env(safe-area-inset-left)); - padding-right: max(25px, env(safe-area-inset-right)); + padding-top: max(25px, env(safe-area-inset-top)); + padding-bottom: max(25px, env(safe-area-inset-bottom)); + padding-left: max(25px, env(safe-area-inset-left)); + padding-right: max(25px, env(safe-area-inset-right)); } .background { - background-color: #FFFFFF; + background-color: #FFFFFF; } .icon { - width: 40px; - height: 40px; - margin-bottom: 1em; + width: 40px; + height: 40px; + margin-bottom: 1em; } h1 { @@ -76,6 +76,40 @@ h2 { align-content: flex-start; } +.actions { + margin-top: 48px; + display: flex; + flex-wrap: wrap; + flex-direction: column; + align-content: flex-start; + justify-content: space-between; +} + +button { + font-family: SFProDisplay-Medium, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 15px; + font-weight: 600; + line-height: 20px; + letter-spacing: -0.2px; + text-align: center; + padding: 12px 16px 12px 16px; + border-radius: 12px; + margin: 4px 0; + width: 100%; +} + +.main-action { + color: white; + background: #3F39E8; + border: 1px solid #3F39E8; +} + +.secondary-action { + color: #3F39E8; + background: #545FF800; + border: 1px solid #545FF866; +} + /** Center the content for iPads **/ @media (min-width: 600px) and (min-height: 600px) { .icon { @@ -117,6 +151,14 @@ h2 { -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } + + .actions { + flex-direction: row-reverse; + } + + button { + width: auto; + } } @@ -132,4 +174,8 @@ h2 { .description, .domain { color: #DBDEE2; } + + .secondary-action { + color: #7C91FF; + } } diff --git a/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift b/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift index b344d0c7f19..a99831f8632 100644 --- a/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift +++ b/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift @@ -355,22 +355,24 @@ extension BrowserViewController: WKNavigationDelegate { if navigationAction.targetFrame?.isMainFrame == true { tab?.updateUserAgent(webView, newURL: requestURL) - let domain = Domain.getOrCreate(forUrl: requestURL, persistent: !isPrivateBrowsing) - - let shouldBlock = await AdBlockStats.shared.shouldBlock( - requestURL: requestURL, sourceURL: requestURL, resourceType: .document, - isAggressiveMode: domain.blockAdsAndTrackingLevel.isAggressive - ) - - if shouldBlock, let escapingURL = requestURL.absoluteString.escape() { - var components = URLComponents(string: InternalURL.baseUrl) - components?.path = "/\(InternalURL.Path.blocked.rawValue)" - components?.queryItems = [URLQueryItem(name: "url", value: escapingURL)] + if let etldP1 = requestURL.baseDomain, tab?.proceedAnywaysDomainList.contains(etldP1) == false { + let domain = Domain.getOrCreate(forUrl: requestURL, persistent: !isPrivateBrowsing) - if let url = components?.url { - let request = PrivilegedRequest(url: url) as URLRequest - tab?.loadRequest(request) - return (.cancel, preferences) + let shouldBlock = await AdBlockStats.shared.shouldBlock( + requestURL: requestURL, sourceURL: requestURL, resourceType: .document, + isAggressiveMode: domain.blockAdsAndTrackingLevel.isAggressive + ) + + if shouldBlock, let escapingURL = requestURL.absoluteString.escape() { + var components = URLComponents(string: InternalURL.baseUrl) + components?.path = "/\(InternalURL.Path.blocked.rawValue)" + components?.queryItems = [URLQueryItem(name: "url", value: escapingURL)] + + if let url = components?.url { + let request = PrivilegedRequest(url: url) as URLRequest + tab?.loadRequest(request) + return (.cancel, preferences) + } } } } diff --git a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift index e575ec52b4a..afe4167c1f1 100644 --- a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift +++ b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift @@ -2503,6 +2503,7 @@ extension BrowserViewController: TabDelegate { ReaderModeScriptHandler(tab: tab), ErrorPageHelper(certStore: profile.certStore), SessionRestoreScriptHandler(tab: tab), + BlockedDomainScriptHandler(tab: tab), PrintScriptHandler(browserController: self, tab: tab), CustomSearchScriptHandler(tab: tab), NightModeScriptHandler(tab: tab), diff --git a/Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift b/Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift index 643fe254de0..bb048f24607 100644 --- a/Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift +++ b/Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift @@ -29,6 +29,10 @@ public class BlockedDomainHandler: InternalSchemeResponse { .replacingOccurrences(of: "%blocked_subtitle%", with: Strings.Shields.domainBlockedPageMessage) .replacingOccurrences(of: "%blocked_domain%", with: originalURL.domainURL.absoluteDisplayString) .replacingOccurrences(of: "%blocked_description%", with: Strings.Shields.domainBlockedPageDescription) + .replacingOccurrences(of: "%proceed_action%", with: Strings.Shields.domainBlockedProceedAction) + .replacingOccurrences(of: "%go_back_action%", with: Strings.Shields.domainBlockedGoBackAction) + .replacingOccurrences(of: "%message_handler%", with: BlockedDomainScriptHandler.messageHandlerName) + .replacingOccurrences(of: "%security_token%", with: UserScriptManager.securityToken) if #available(iOS 16.0, *) { html = html?.replacingOccurrences(of: "", with: "") diff --git a/Sources/Brave/Frontend/Browser/Tab.swift b/Sources/Brave/Frontend/Browser/Tab.swift index 261b154ed7e..606701b4b0e 100644 --- a/Sources/Brave/Frontend/Browser/Tab.swift +++ b/Sources/Brave/Frontend/Browser/Tab.swift @@ -593,6 +593,9 @@ class Tab: NSObject { } return favicon } + + /// A list of domains that we want to proceed to anyways regardless of any ad-blocking + var proceedAnywaysDomainList: Set = [] var canGoBack: Bool { return webView?.canGoBack ?? false diff --git a/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/BlockedDomainScriptHandler.swift b/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/BlockedDomainScriptHandler.swift new file mode 100644 index 00000000000..95dcce2d86c --- /dev/null +++ b/Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/BlockedDomainScriptHandler.swift @@ -0,0 +1,73 @@ +// Copyright 2023 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 Foundation +import Shared +import WebKit + +class BlockedDomainScriptHandler: TabContentScript { + private weak var tab: Tab? + + required init(tab: Tab) { + self.tab = tab + } + + static let scriptName = "BlockedDomainScript" + static let scriptId = UUID().uuidString + static let messageHandlerName = "\(scriptName)_\(messageUUID)" + static let scriptSandbox: WKContentWorld = .page + static let userScript: WKUserScript? = nil + + func userContentController(_ userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage, replyHandler: (Any?, String?) -> Void) { + defer { replyHandler(nil, nil) } + + if !verifyMessage(message: message, securityToken: UserScriptManager.securityToken) { + assertionFailure("Missing required security token.") + return + } + + guard let params = message.body as? [String: AnyObject], let action = params["action"] as? String else { + assertionFailure("Missing required params.") + return + } + + switch action { + case "didProceed": + blockedDomainDidProceed() + case "didGoBack": + blockedDomainDidGoBack() + default: + assertionFailure("Unhandled action `\(action)`") + } + } + + private func blockedDomainDidProceed() { + guard let url = tab?.url?.stippedInternalURL, let etldP1 = url.baseDomain else { + assertionFailure("There should be no way this method can be triggered if the tab is not on an internal url") + return + } + + let request = URLRequest(url: url) + tab?.proceedAnywaysDomainList.insert(etldP1) + tab?.loadRequest(request) + } + + private func blockedDomainDidGoBack() { + guard let url = tab?.url?.stippedInternalURL else { + assertionFailure("There should be no way this method can be triggered if the tab is not on an internal url") + return + } + + guard let listItem = tab?.backList?.reversed().first(where: { $0.url != url }) else { + // How is this even possible? + // All testing indicates no, so we will not handle. + // If we find it is, then we need to disable or hide the "Go Back" button in these cases. + // But this would require heavy changes or ugly mechanisms to InternalSchemeHandler. + return + } + + tab?.goToBackForwardListItem(listItem) + } +} diff --git a/Sources/BraveShields/ShieldStrings.swift b/Sources/BraveShields/ShieldStrings.swift index 21e1e39af20..e54fef2b7ee 100644 --- a/Sources/BraveShields/ShieldStrings.swift +++ b/Sources/BraveShields/ShieldStrings.swift @@ -195,4 +195,18 @@ public extension Strings.Shields { value: "Because you requested to aggressively block trackers and ads, Brave is blocking this site before the first network connection.", comment: "A description in the warning page that appears when a page was blocked" ) + + /// Text for a button in a blocked page info screen that allows you to proceed regardless of the privacy warning + static let domainBlockedProceedAction = NSLocalizedString( + "DomainBlockedProceedAction", tableName: "BraveShared", bundle: .module, + value: "Proceed", + comment: "Text for a button in a blocked page info screen that allows you to proceed regardless of the privacy warning" + ) + + /// A description in the warning page that appears when a page was blocked + static let domainBlockedGoBackAction = NSLocalizedString( + "DomainBlockedGoBackAction", tableName: "BraveShared", bundle: .module, + value: "Go Back", + comment: "Text for a button in a blocked page info screen that takes you back where you came from" + ) }