From 1d06450272cc216c722c32bbd18e1acb4f023736 Mon Sep 17 00:00:00 2001 From: Jacob Sikorski Date: Mon, 5 Feb 2024 12:35:58 -0700 Subject: [PATCH] Fix #8096: Add blocked page interstitial (#8589) * Fix #8096: Add blocked page interstitial * Add domain blocking "Proceed" and "Go Back" actions (#8628) --- App/iOS/Delegates/AppState.swift | 3 +- Package.swift | 3 + .../Images/warning-triangle-outline.svg | 10 + .../Pages/BlockedDomain.html | 48 +++++ .../Scripts/BlockedDomain.js | 0 .../Styles/BlockedDomain.css | 181 ++++++++++++++++++ .../BVC+WKNavigationDelegate.swift | 21 ++ .../BrowserViewController.swift | 1 + .../Handlers/BlockedDomainHandler.swift | 47 +++++ .../InternalSchemeHandler.swift | 2 + Sources/Brave/Frontend/Browser/Tab.swift | 3 + .../Internal/BlockedDomainScriptHandler.swift | 73 +++++++ .../Brave/WebFilters/AdblockRustEngine.swift | 1 + .../Extensions/URLExtensions.swift | 7 +- Sources/BraveShields/ShieldStrings.swift | 49 ++++- Sources/Shared/Extensions/URLExtensions.swift | 13 +- 16 files changed, 456 insertions(+), 6 deletions(-) create mode 100644 Sources/Brave/Assets/Interstitial Pages/Images/warning-triangle-outline.svg create mode 100644 Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html create mode 100644 Sources/Brave/Assets/Interstitial Pages/Scripts/BlockedDomain.js create mode 100644 Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css create mode 100644 Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift create mode 100644 Sources/Brave/Frontend/UserContent/UserScripts/Scripts_Dynamic/ScriptHandlers/Internal/BlockedDomainScriptHandler.swift diff --git a/App/iOS/Delegates/AppState.swift b/App/iOS/Delegates/AppState.swift index c2094e98b8d..813d98705f1 100644 --- a/App/iOS/Delegates/AppState.swift +++ b/App/iOS/Delegates/AppState.swift @@ -192,7 +192,8 @@ public class AppState { (ErrorPageHandler.path, ErrorPageHandler()), (ReaderModeHandler.path, ReaderModeHandler(profile: profile)), (IPFSSchemeHandler.path, IPFSSchemeHandler()), - (Web3DomainHandler.path, Web3DomainHandler()) + (Web3DomainHandler.path, Web3DomainHandler()), + (BlockedDomainHandler.path, BlockedDomainHandler()) ] responders.forEach { (path, responder) in diff --git a/Package.swift b/Package.swift index b47c23d2330..b5de278e499 100644 --- a/Package.swift +++ b/Package.swift @@ -376,6 +376,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"), @@ -391,6 +392,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..b79155175a4 --- /dev/null +++ b/Sources/Brave/Assets/Interstitial Pages/Pages/BlockedDomain.html @@ -0,0 +1,48 @@ + + + + + + + + %page_title% + + + + +
+ Icon +

%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 new file mode 100644 index 00000000000..b00c2c7f892 --- /dev/null +++ b/Sources/Brave/Assets/Interstitial Pages/Styles/BlockedDomain.css @@ -0,0 +1,181 @@ +/* + 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; +} + +.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 { + 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%); + } + + .actions { + flex-direction: row-reverse; + } + + button { + width: auto; + } +} + + +@media (prefers-color-scheme: dark) { + .background { + background-color: #0D0F14; + } + + h1, h2 { + color: #F6F7F8; + } + + .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 48b717f99ef..0a58c67f48e 100644 --- a/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift +++ b/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift @@ -356,6 +356,27 @@ extension BrowserViewController: WKNavigationDelegate { if ["http", "https", "data", "blob", "file"].contains(requestURL.scheme) { if navigationAction.targetFrame?.isMainFrame == true { tab?.updateUserAgent(webView, newURL: requestURL) + + if let etldP1 = requestURL.baseDomain, tab?.proceedAnywaysDomainList.contains(etldP1) == false { + 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 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/BrowserViewController/BrowserViewController.swift b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift index 593b29935fa..4c9f461078b 100644 --- a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift +++ b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController.swift @@ -2507,6 +2507,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 new file mode 100644 index 00000000000..bb048f24607 --- /dev/null +++ b/Sources/Brave/Frontend/Browser/Handlers/BlockedDomainHandler.swift @@ -0,0 +1,47 @@ +// 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.domainBlockedTitle) + .replacingOccurrences(of: "%blocked_title%", with: Strings.Shields.domainBlockedPageTitle) + .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: "") + } + + 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/Frontend/Browser/Tab.swift b/Sources/Brave/Frontend/Browser/Tab.swift index 8e1a621fd04..23e0e385035 100644 --- a/Sources/Brave/Frontend/Browser/Tab.swift +++ b/Sources/Brave/Frontend/Browser/Tab.swift @@ -594,6 +594,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/Brave/WebFilters/AdblockRustEngine.swift b/Sources/Brave/WebFilters/AdblockRustEngine.swift index 24ab0ec9602..c0bd3daf7ae 100644 --- a/Sources/Brave/WebFilters/AdblockRustEngine.swift +++ b/Sources/Brave/WebFilters/AdblockRustEngine.swift @@ -11,6 +11,7 @@ extension AdblockEngine { case xmlhttprequest case script case image + case document case subdocument } 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 20e6bd2b2a7..e54fef2b7ee 100644 --- a/Sources/BraveShields/ShieldStrings.swift +++ b/Sources/BraveShields/ShieldStrings.swift @@ -147,7 +147,8 @@ public extension Strings.Shields { ) } -// MARK: - Shields +// MARK: - GPC + public extension Strings.Shields { /// A label of the GPC toggle static let enableGPCLabel = NSLocalizedString( @@ -163,3 +164,49 @@ public extension Strings.Shields { comment: "A description of what the Enable GPC toggle does" ) } + +// MARK: - Blocked Page + +public extension Strings.Shields { + /// A tab title that appears when a page was blocked + static let domainBlockedTitle = 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 domainBlockedPageTitle = NSLocalizedString( + "DomainBlockedPageTitle", 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 domainBlockedPageMessage = NSLocalizedString( + "DomainBlockedPageMessage", 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 domainBlockedPageDescription = NSLocalizedString( + "DomainBlockedPageDescription", 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" + ) + + /// 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" + ) +} diff --git a/Sources/Shared/Extensions/URLExtensions.swift b/Sources/Shared/Extensions/URLExtensions.swift index e06d3695dfa..08f99e5ff96 100644 --- a/Sources/Shared/Extensions/URLExtensions.swift +++ b/Sources/Shared/Extensions/URLExtensions.swift @@ -146,7 +146,7 @@ extension URL { return internalUrl.originalURLFromErrorPage?.displayURL } - if let internalUrl = InternalURL(self), internalUrl.isSessionRestore || internalUrl.isWeb3URL { + if let internalUrl = InternalURL(self), internalUrl.isSessionRestore || internalUrl.isWeb3URL || internalUrl.isBlockedPage { return internalUrl.extractedUrlParam?.displayURL } @@ -503,10 +503,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 +573,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) }