diff --git a/Client.xcodeproj/project.pbxproj b/Client.xcodeproj/project.pbxproj index 4e56d3129ea..f55c5a7798a 100644 --- a/Client.xcodeproj/project.pbxproj +++ b/Client.xcodeproj/project.pbxproj @@ -709,6 +709,7 @@ 5E46C37C234FB9A700ACA8C1 /* self-signed.cer in Resources */ = {isa = PBXBuildFile; fileRef = 5E46C37B234FB9A700ACA8C1 /* self-signed.cer */; }; 5E4845C022DE381200372022 /* WindowRenderHelper.js in Resources */ = {isa = PBXBuildFile; fileRef = 5E4845BF22DE381200372022 /* WindowRenderHelper.js */; }; 5E4845C222DE3DF800372022 /* WindowRenderHelperScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E4845C122DE3DF800372022 /* WindowRenderHelperScript.swift */; }; + 5E4E078324A0E4D700B01720 /* YoutubeAdblock.js in Resources */ = {isa = PBXBuildFile; fileRef = 5E4E078224A0E4D700B01720 /* YoutubeAdblock.js */; }; 5E612A8C234B7FC1007D12B5 /* OnboardingAdsAvailableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E1D8C66232BE43100BDE662 /* OnboardingAdsAvailableController.swift */; }; 5E612A8E234B7FC8007D12B5 /* OnboardingAdsAvailableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E1D8C68232BE47A00BDE662 /* OnboardingAdsAvailableView.swift */; }; 5E612A8F234B7FCA007D12B5 /* OnboardingRewardsAgreementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E1D8C6A232BF95200BDE662 /* OnboardingRewardsAgreementViewController.swift */; }; @@ -2223,6 +2224,7 @@ 5E46C37B234FB9A700ACA8C1 /* self-signed.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = "self-signed.cer"; sourceTree = ""; }; 5E4845BF22DE381200372022 /* WindowRenderHelper.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = WindowRenderHelper.js; sourceTree = ""; }; 5E4845C122DE3DF800372022 /* WindowRenderHelperScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowRenderHelperScript.swift; sourceTree = ""; }; + 5E4E078224A0E4D700B01720 /* YoutubeAdblock.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = YoutubeAdblock.js; sourceTree = ""; }; 5E6683A823D61CF7005B3A6C /* NTPDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NTPDownloader.swift; sourceTree = ""; }; 5E77F9DC236BB68700E1649C /* AttestationDebugger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttestationDebugger.swift; sourceTree = ""; }; 5E856FE5235E08110094E113 /* Cryptography.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Cryptography.swift; sourceTree = ""; tabWidth = 2; }; @@ -4475,6 +4477,7 @@ 5E4845BF22DE381200372022 /* WindowRenderHelper.js */, 5EB57D0122FDC0CB00A07325 /* FullscreenHelper.js */, F9B23EE123F61173000EB3D8 /* PaymentRequest.js */, + 5E4E078224A0E4D700B01720 /* YoutubeAdblock.js */, ); path = UserScripts; sourceTree = ""; @@ -5872,6 +5875,7 @@ E4B7B7861A793CF20022C5E0 /* FiraSans-UltraLight.ttf in Resources */, E4B7B77D1A793CF20022C5E0 /* FiraSans-Regular.ttf in Resources */, 4422D55221BFFB7E00BF1855 /* make_unicode_casefold.py in Resources */, + 5E4E078324A0E4D700B01720 /* YoutubeAdblock.js in Resources */, E4B7B7791A793CF20022C5E0 /* FiraSans-Light.ttf in Resources */, 5E3477E922D7771700B0D5F8 /* ResourceDownloader.js in Resources */, F99505FF22937E3900CC6543 /* U2F-low-level.js in Resources */, diff --git a/Client/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift b/Client/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift index 693b359d608..eedff63e437 100644 --- a/Client/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift +++ b/Client/Frontend/Browser/BrowserViewController/BrowserViewController+WKNavigationDelegate.swift @@ -188,7 +188,14 @@ extension BrowserViewController: WKNavigationDelegate { decisionHandler(.cancel) return } + let isPrivateBrowsing = PrivateBrowsingManager.shared.isPrivateBrowsing + + if url.baseDomain == "youtube.com" { + let domain = Domain.getOrCreate(forUrl: url, persistent: !isPrivateBrowsing) + tabManager[webView]?.userScriptManager?.isYoutubeAdblockEnabled = domain.isShieldExpected(.AdblockAndTp, considerAllShieldsOption: true) + } + // This is the normal case, opening a http or https url, which we handle by loading them in this WKWebView. We // always allow this. Additionally, data URIs are also handled just like normal web pages. diff --git a/Client/Frontend/Browser/Tab.swift b/Client/Frontend/Browser/Tab.swift index 7b91c24e3b6..fa2148b85a8 100644 --- a/Client/Frontend/Browser/Tab.swift +++ b/Client/Frontend/Browser/Tab.swift @@ -242,7 +242,8 @@ class Tab: NSObject { isFingerprintingProtectionEnabled: Preferences.Shields.fingerprintingProtection.value, isCookieBlockingEnabled: Preferences.Privacy.blockAllCookies.value, isU2FEnabled: webView.hasOnlySecureContent, - isPaymentRequestEnabled: webView.hasOnlySecureContent) + isPaymentRequestEnabled: webView.hasOnlySecureContent, + isYoutubeAdblockEnabled: false) tabDelegate?.tab?(self, didCreateWebView: webView) } } diff --git a/Client/Frontend/Browser/UserScriptManager.swift b/Client/Frontend/Browser/UserScriptManager.swift index 23794e43e45..a6f81d56c24 100644 --- a/Client/Frontend/Browser/UserScriptManager.swift +++ b/Client/Frontend/Browser/UserScriptManager.swift @@ -45,12 +45,21 @@ class UserScriptManager { } } - init(tab: Tab, isFingerprintingProtectionEnabled: Bool, isCookieBlockingEnabled: Bool, isU2FEnabled: Bool, isPaymentRequestEnabled: Bool) { + // Whether or not the Adblock shields are enabled + var isYoutubeAdblockEnabled: Bool { + didSet { + if oldValue == isYoutubeAdblockEnabled { return } + reloadUserScripts() + } + } + + init(tab: Tab, isFingerprintingProtectionEnabled: Bool, isCookieBlockingEnabled: Bool, isU2FEnabled: Bool, isPaymentRequestEnabled: Bool, isYoutubeAdblockEnabled: Bool) { self.tab = tab self.isFingerprintingProtectionEnabled = isFingerprintingProtectionEnabled self.isCookieBlockingEnabled = isCookieBlockingEnabled self.isU2FEnabled = isU2FEnabled self.isPaymentRequestEnabled = isPaymentRequestEnabled + self.isYoutubeAdblockEnabled = isYoutubeAdblockEnabled reloadUserScripts() } @@ -187,7 +196,7 @@ class UserScriptManager { //Verify that the application itself is making a call to the JS script instead of other scripts on the page. //This variable will be unique amongst scripts loaded in the page. - //When the script is called, the token is provided in order to access teh script variable. + //When the script is called, the token is provided in order to access the script variable. var alteredSource = source let token = UserScriptManager.securityToken.uuidString.replacingOccurrences(of: "-", with: "", options: .literal) alteredSource = alteredSource.replacingOccurrences(of: "$", with: "PSF\(token)", options: .literal) @@ -197,6 +206,23 @@ class UserScriptManager { return WKUserScript(source: alteredSource, injectionTime: .atDocumentStart, forMainFrameOnly: false) }() + + private let youtubeAdblockJSScript: WKUserScript? = { + guard let path = Bundle.main.path(forResource: "YoutubeAdblock", ofType: "js"), let source = try? String(contentsOfFile: path) else { + log.error("Failed to load YoutubeAdblock.js") + return nil + } + + //Verify that the application itself is making a call to the JS script instead of other scripts on the page. + //This variable will be unique amongst scripts loaded in the page. + //When the script is called, the token is provided in order to access the script variable. + var alteredSource = source + let token = UserScriptManager.securityToken.uuidString.replacingOccurrences(of: "-", with: "", options: .literal) + alteredSource = alteredSource.replacingOccurrences(of: "$", with: "ABS\(token)", options: .literal) + alteredSource = alteredSource.replacingOccurrences(of: "$", with: "ABS\(token)", options: .literal) + + return WKUserScript(source: alteredSource, injectionTime: .atDocumentStart, forMainFrameOnly: false) + }() private func reloadUserScripts() { tab?.webView?.configuration.userContentController.do { @@ -230,6 +256,10 @@ class UserScriptManager { $0.addUserScript(script) } + if isYoutubeAdblockEnabled, let script = youtubeAdblockJSScript { + $0.addUserScript(script) + } + #if !NO_SKUS if isPaymentRequestEnabled, let script = PaymentRequestUserScript { $0.addUserScript(script) diff --git a/Client/Frontend/UserContent/UserScripts/YoutubeAdblock.js b/Client/Frontend/UserContent/UserScripts/YoutubeAdblock.js new file mode 100644 index 00000000000..c39beba44d0 --- /dev/null +++ b/Client/Frontend/UserContent/UserScripts/YoutubeAdblock.js @@ -0,0 +1,36 @@ +// Copyright 2020 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 http://mozilla.org/MPL/2.0/. + +(function() { + const $ = ['playerResponse.adPlacements', 'playerResponse.playerAds', 'adPlacements', 'playerAds']; + const $ = function(root, path) { + let owner = root; + let chain = path; + while(true) { + if (owner instanceof Object === false) { return; } + const pos = chain.indexOf('.'); + if (pos === -1) { + return owner.hasOwnProperty(chain)? [owner, chain]: undefined; + } + const prop = chain.slice(0, pos); + if (owner.hasOwnProperty(prop) === false) { return; } + owner = owner[prop]; + chain = chain.slice(pos + 1); + } + }; + + JSON.parse = new Proxy(JSON.parse, { + apply: function() { + const r = Reflect.apply(...arguments); + for (const path of $) { + const details = $(r, path); + if (details !== undefined) { + delete details[0][details[1]]; + } + } + return r; + }, + }); +})();