diff --git a/App/iOS/Delegates/AppState.swift b/App/iOS/Delegates/AppState.swift index eade1547a26..27b5acdbfb1 100644 --- a/App/iOS/Delegates/AppState.swift +++ b/App/iOS/Delegates/AppState.swift @@ -191,7 +191,8 @@ public class AppState { (ErrorPageHandler.path, ErrorPageHandler()), (ReaderModeHandler.path, ReaderModeHandler(profile: profile)), (IPFSSchemeHandler.path, IPFSSchemeHandler()), - (Web3DomainHandler.path, Web3DomainHandler()) + (Web3DomainHandler.path, Web3DomainHandler()), + (InternalURL.Path.blocked.rawValue, BlockedDomainHandler()) ] responders.forEach { (path, responder) in diff --git a/Package.swift b/Package.swift index 201752d59c0..e264ffe2bb6 100644 --- a/Package.swift +++ b/Package.swift @@ -377,6 +377,7 @@ var braveTarget: PackageDescription.Target = .target( .copy("Assets/Fonts/NewYorkMedium-BoldItalic.otf"), .copy("Assets/Fonts/NewYorkMedium-Regular.otf"), .copy("Assets/Fonts/NewYorkMedium-RegularItalic.otf"), + .copy("Assets/Interstitial Pages/Pages/BlockedDomain.html"), .copy("Assets/Interstitial Pages/Pages/CertificateError.html"), .copy("Assets/Interstitial Pages/Pages/GenericError.html"), .copy("Assets/Interstitial Pages/Pages/NetworkError.html"), @@ -392,6 +393,8 @@ var braveTarget: PackageDescription.Target = .target( .copy("Assets/Interstitial Pages/Images/Warning.svg"), .copy("Assets/Interstitial Pages/Images/BraveIPFS.svg"), .copy("Assets/Interstitial Pages/Images/IPFSBackground.svg"), + .copy("Assets/Interstitial Pages/Images/warning-triangle-outline.svg"), + .copy("Assets/Interstitial Pages/Styles/BlockedDomain.css"), .copy("Assets/Interstitial Pages/Styles/CertificateError.css"), .copy("Assets/Interstitial Pages/Styles/InterstitialStyles.css"), .copy("Assets/Interstitial Pages/Styles/NetworkError.css"), diff --git a/Sources/Brave/Assets/Interstitial Pages/Images/warning-triangle-outline.svg b/Sources/Brave/Assets/Interstitial Pages/Images/warning-triangle-outline.svg new file mode 100644 index 00000000000..8ef3c0d08de --- /dev/null +++ b/Sources/Brave/Assets/Interstitial Pages/Images/warning-triangle-outline.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html b/Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html new file mode 100644 index 00000000000..b75fc84dc3e --- /dev/null +++ b/Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html @@ -0,0 +1,25 @@ + + + + + + + + %page_title% + + + + +
+ Icon +

%blocked_title%

+

%blocked_subtitle%

+
%blocked_domain%
+

%blocked_description%

+
+ + diff --git a/Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css b/Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css new file mode 100644 index 00000000000..21fc3cdc934 --- /dev/null +++ b/Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css @@ -0,0 +1,135 @@ +/* + Copyright (c) 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/. + */ + +html { + overscroll-behavior: none; +} + +.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)); +} + +.background { + background-color: #FFFFFF; +} + +.icon { + width: 40px; + height: 40px; + margin-bottom: 1em; +} + +h1 { + font-family: SFProDisplay-Medium, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 22px; + font-weight: 500; + line-height: 28px; + letter-spacing: 0.35px; + text-align: left; + color: #0D0F14; + margin-bottom: 0px; +} + +h2 { + font-family: SFProDisplay-Medium, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 17px; + font-weight: 600; + line-height: 22px; + letter-spacing: -0.2px; + text-align: left; + color: #0D0F14; + margin-bottom: 0px; +} + +.description { + font-family: SFProText-Regular, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 15px; + font-weight: 400; + line-height: 20px; + letter-spacing: -0.2px; + text-align: left; + color: #3F4855; + margin-bottom: 0px; +} + +.domain { + font-size: 15px; + font-weight: 400; + line-height: 20px; + letter-spacing: 0em; + text-align: left; + color: #3F4855; + margin-bottom: 0px; +} + +.container { + display: flex; + flex-wrap: wrap; + flex-direction: column; + align-content: flex-start; +} + +/** Center the content for iPads **/ +@media (min-width: 600px) and (min-height: 600px) { + .icon { + width: 64px; + height: 64px; + } + + h1 { + font-size: 28px; + font-weight: 500; + line-height: 36px; + letter-spacing: 0em; + } + + h2 { + font-size: 16px; + line-height: 26px; + } + + .description { + font-size: 14px; + line-height: 22px; + letter-spacing: -0.1px; + } + + .domain { + font-size: 14px; + font-weight: 400; + line-height: 22px; + letter-spacing: 0em; + } + + .content { + margin: 0; + position: absolute; + top: 40%; + left: 50%; + max-width: 650px; + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + } +} + + +@media (prefers-color-scheme: dark) { + .background { + background-color: #0D0F14; + } + + h1, h2 { + color: #F6F7F8; + } + + .description, .domain { + color: #DBDEE2; + } +} diff --git a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift index 11968302b83..13f8cd00598 100644 --- a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift +++ b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift @@ -349,6 +349,25 @@ extension BrowserViewController: WKNavigationDelegate { if ["http", "https", "data", "blob", "file"].contains(requestURL.scheme) { 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 { + var components = URLComponents(string: InternalURL.baseUrl) + components?.path = "/\(InternalURL.Path.blocked.rawValue)" + components?.queryItems = [URLQueryItem(name: "url", value: requestURL.absoluteString.escape()!)] + + if let url = components?.url { + let request = PrivilegedRequest(url: url) as URLRequest + tab?.loadRequest(request) + return (.cancel, preferences) + } + } } pendingRequests[requestURL.absoluteString] = navigationAction.request diff --git a/Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift b/Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift new file mode 100644 index 00000000000..279318659cd --- /dev/null +++ b/Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift @@ -0,0 +1,43 @@ +// 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 WebKit +import Shared +import BraveShared +import BraveShields + +public class BlockedDomainHandler: InternalSchemeResponse { + public static let path = InternalURL.Path.blocked.rawValue + + public init() {} + + public func response(forRequest request: URLRequest) -> (URLResponse, Data)? { + guard let url = request.url, let internalURL = InternalURL(url), let originalURL = internalURL.extractedUrlParam else { return nil } + let response = InternalSchemeHandler.response(forUrl: internalURL.url) + + guard let asset = Bundle.module.path(forResource: "BlockedDomain", ofType: "html") else { + assert(false) + return nil + } + + var html = try? String(contentsOfFile: asset) + .replacingOccurrences(of: "%page_title%", with: Strings.Shields.domianBlockedTitle) + .replacingOccurrences(of: "%blocked_title%", with: Strings.Shields.domianBlockedPageTitle) + .replacingOccurrences(of: "%blocked_subtitle%", with: Strings.Shields.domianBlockedPageMessage) + .replacingOccurrences(of: "%blocked_domain%", with: originalURL.domainURL.absoluteDisplayString) + .replacingOccurrences(of: "%blocked_description%", with: Strings.Shields.domianBlockedPageDescription) + + if #available(iOS 16.0, *) { + html = html?.replacingOccurrences(of: "", with: "") + } + + guard let data = html?.data(using: .utf8) else { + return nil + } + + return (response, data) + } +} diff --git a/Sources/Brave/Frontend/Browser/Interstitial Pages/InternalSchemeHandler.swift b/Sources/Brave/Frontend/Browser/Interstitial Pages/InternalSchemeHandler.swift index 5db2e03bcf0..974c8828f1f 100644 --- a/Sources/Brave/Frontend/Browser/Interstitial Pages/InternalSchemeHandler.swift +++ b/Sources/Brave/Frontend/Browser/Interstitial Pages/InternalSchemeHandler.swift @@ -35,6 +35,7 @@ public class InternalSchemeHandler: NSObject, WKURLSchemeHandler { let allowedInternalResources = [ // interstitial "/interstitial-style/InterstitialStyles.css": "text/css", + "/interstitial-style/BlockedDomain.css": "text/css", "/interstitial-style/NetworkError.css": "text/css", "/interstitial-style/CertificateError.css": "text/css", "/interstitial-style/Web3Domain.css": "text/css", @@ -49,6 +50,7 @@ public class InternalSchemeHandler: NSObject, WKURLSchemeHandler { "/interstitial-icon/Carret.png": "image/png", "/interstitial-icon/BraveIPFS.svg": "image/svg+xml", "/interstitial-icon/IPFSBackground.svg": "image/svg+xml", + "/interstitial-icon/warning-triangle-outline.svg": "image/svg+xml", // readermode "/\(InternalURL.Path.readermode.rawValue)/styles/Reader.css": "text/css", diff --git a/Sources/Brave/WebFilters/AdblockRustEngine.swift b/Sources/Brave/WebFilters/AdblockRustEngine.swift index 803aac3c67e..432d416f7f4 100644 --- a/Sources/Brave/WebFilters/AdblockRustEngine.swift +++ b/Sources/Brave/WebFilters/AdblockRustEngine.swift @@ -8,7 +8,7 @@ import BraveCore extension AdblockEngine { public enum ResourceType: String, Decodable { - case xmlhttprequest, script, image + case xmlhttprequest, script, image, document } /// Check the rust engine if the request should be blocked given the `sourceURL` and `resourceType`. diff --git a/Sources/BraveShared/Extensions/URLExtensions.swift b/Sources/BraveShared/Extensions/URLExtensions.swift index a8d81717b07..79708464377 100644 --- a/Sources/BraveShared/Extensions/URLExtensions.swift +++ b/Sources/BraveShared/Extensions/URLExtensions.swift @@ -34,7 +34,7 @@ extension URL { switch internalURL.urlType { case .errorPage: return internalURL.originalURLFromErrorPage - case .web3Page, .sessionRestorePage, .readerModePage, .aboutHomePage: + case .web3Page, .sessionRestorePage, .readerModePage, .aboutHomePage, .blockedPage: return internalURL.extractedUrlParam default: return nil @@ -48,6 +48,7 @@ extension URL { extension InternalURL { enum URLType { + case blockedPage case sessionRestorePage case errorPage case readerModePage @@ -57,6 +58,10 @@ extension InternalURL { } var urlType: URLType { + if isBlockedPage { + return .blockedPage + } + if isErrorPage { return .errorPage } diff --git a/Sources/BraveShields/ShieldStrings.swift b/Sources/BraveShields/ShieldStrings.swift index 3cebb060082..a3cdb055619 100644 --- a/Sources/BraveShields/ShieldStrings.swift +++ b/Sources/BraveShields/ShieldStrings.swift @@ -146,3 +146,35 @@ public extension Strings.Shields { comment: "A button that ignores the brave player" ) } + +// MARK: - Blocked Page + +public extension Strings.Shields { + /// A tab title that appears when a page was blocked + static let domianBlockedTitle = NSLocalizedString( + "DomainBlockedTitle", tableName: "BraveShared", bundle: .module, + value: "Domain Blocked", + comment: "A tab title for the warning page that appears when a page was blocked" + ) + + /// A title in the warning page that appears when a page was blocked + static let domianBlockedPageTitle = NSLocalizedString( + "DomianBlockedPageTitle", tableName: "BraveShared", bundle: .module, + value: "This Site May Attempt to Track You Across Other Sites", + comment: "A title in the warning page that appears when a page was blocked" + ) + + /// A title in the warning page that appears when a page was blocked + static let domianBlockedPageMessage = NSLocalizedString( + "DomianBlockedPageMessage", tableName: "BraveShared", bundle: .module, + value: "Brave has prevented the following site from loading:", + comment: "A message in the warning page that appears when a page was blocked" + ) + + /// A description in the warning page that appears when a page was blocked + static let domianBlockedPageDescription = NSLocalizedString( + "DomianBlockedPageDescription", tableName: "BraveShared", bundle: .module, + 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" + ) +} diff --git a/Sources/Shared/Extensions/URLExtensions.swift b/Sources/Shared/Extensions/URLExtensions.swift index e06d3695dfa..b82ffc503b7 100644 --- a/Sources/Shared/Extensions/URLExtensions.swift +++ b/Sources/Shared/Extensions/URLExtensions.swift @@ -149,6 +149,10 @@ extension URL { if let internalUrl = InternalURL(self), internalUrl.isSessionRestore || internalUrl.isWeb3URL { return internalUrl.extractedUrlParam?.displayURL } + + if let internalUrl = InternalURL(self), internalUrl.isBlockedPage { + return internalUrl.extractedUrlParam?.displayURL + } if !InternalURL.isValid(url: self) { let url = self.havingRemovedAuthorisationComponents() @@ -503,10 +507,13 @@ public struct InternalURL { public static let scheme = "internal" public static let host = "local" public static let baseUrl = "\(scheme)://\(host)" + public enum Path: String { - case errorpage = "errorpage" - case sessionrestore = "sessionrestore" + case errorpage + case sessionrestore case readermode = "reader-mode" + case blocked + func matches(_ string: String) -> Bool { return string.range(of: "/?\(self.rawValue)", options: .regularExpression, range: nil, locale: nil) != nil } @@ -570,6 +577,10 @@ public struct InternalURL { return InternalURL.Path.errorpage.matches(path ?? "") } + public var isBlockedPage: Bool { + return InternalURL.Path.blocked.matches(url.path) + } + public var isReaderModePage: Bool { return InternalURL.Path.readermode.matches(url.path) }