From 4bfd14ae3bb3c8ff13fb7532c30ec32ed9e37602 Mon Sep 17 00:00:00 2001 From: Anton Lazarev Date: Thu, 19 Dec 2019 16:21:35 -0800 Subject: [PATCH] Revert "Merge pull request #4255 from brave/bsc-revert-cosmetic-filtering" This reverts commit fcfe38a5494af418f94e6f05d9d271d7f74cea35, reversing changes made to 950778a7a18d331e5fae758ef897f2538bd1e1d3. --- DEPS | 2 +- browser/extensions/BUILD.gn | 2 + browser/extensions/api/brave_shields_api.cc | 45 +++++ browser/extensions/api/brave_shields_api.h | 32 ++++ chromium_src/chrome/browser/about_flags.cc | 6 + .../chrome/browser/flag_descriptions.cc | 12 ++ .../chrome/browser/flag_descriptions.h | 16 ++ chromium_src/chrome/common/chrome_features.cc | 12 ++ chromium_src/chrome/common/chrome_features.h | 16 ++ common/extensions/api/brave_shields.json | 76 ++++++++ .../extension/brave_extension/BUILD.gn | 1 + .../actions/shieldsPanelActions.ts | 25 +++ .../background/api/cosmeticFilterAPI.ts | 57 +++++- .../background/api/shieldsAPI.ts | 7 +- .../background/events/cosmeticFilterEvents.ts | 28 +++ .../reducers/shieldsPanelReducer.ts | 52 ++++- .../constants/shieldsPanelTypes.ts | 3 + .../extension/brave_extension/content.ts | 11 ++ .../brave_extension/content_cosmetic.ts | 80 ++++++++ .../extension/brave_extension/manifest.json | 3 +- .../extension/brave_extension/notes.md | 0 .../state/shieldsPanelState.ts | 6 + .../types/actions/shieldsPanelActions.ts | 37 +++- .../types/adblock/adblockTypes.ts | 4 + .../types/constants/shieldsPanelTypes.ts | 3 + .../types/state/shieldsPannelState.ts | 7 + components/brave_shields/browser/BUILD.gn | 1 + .../browser/ad_block_base_service.cc | 14 ++ .../browser/ad_block_base_service.h | 8 + .../browser/ad_block_service_browsertest.cc | 180 ++++++++++++++++++ components/definitions/chromel.d.ts | 10 + .../background/api/cosmeticFilterAPI_test.ts | 10 +- .../background/api/shieldsAPI_test.ts | 2 + components/test/testData.ts | 3 + patches/chrome-browser-about_flags.cc.patch | 8 + test/data/cosmetic_filtering.html | 41 ++++ 36 files changed, 807 insertions(+), 13 deletions(-) create mode 100644 chromium_src/chrome/browser/flag_descriptions.cc create mode 100644 chromium_src/chrome/browser/flag_descriptions.h create mode 100644 chromium_src/chrome/common/chrome_features.cc create mode 100644 chromium_src/chrome/common/chrome_features.h create mode 100644 components/brave_extension/extension/brave_extension/content_cosmetic.ts create mode 100644 components/brave_extension/extension/brave_extension/notes.md create mode 100644 test/data/cosmetic_filtering.html diff --git a/DEPS b/DEPS index 86c2898e8dc2..b78ac1a76884 100644 --- a/DEPS +++ b/DEPS @@ -1,7 +1,7 @@ use_relative_paths = True deps = { - "vendor/adblock_rust_ffi": "https://github.com/brave/adblock-rust-ffi.git@89127a30655eaf54cf73794309846084ea8b91b9", + "vendor/adblock_rust_ffi": "https://github.com/brave/adblock-rust-ffi.git@d757c647699ff7e6dacc5d7bbd51649b509609a5", "vendor/autoplay-whitelist": "https://github.com/brave/autoplay-whitelist.git@ea527a4d36051daedb34421e129c98eda06cb5d3", "vendor/extension-whitelist": "https://github.com/brave/extension-whitelist.git@7843f62e26a23c51336330e220e9d7992680aae9", "vendor/hashset-cpp": "https://github.com/brave/hashset-cpp.git@6eab0271d014ff09bd9f38abe1e0c117e13e9aa9", diff --git a/browser/extensions/BUILD.gn b/browser/extensions/BUILD.gn index 0fad533b7e0b..c1f2ba2b7af7 100644 --- a/browser/extensions/BUILD.gn +++ b/browser/extensions/BUILD.gn @@ -70,8 +70,10 @@ source_set("extensions") { "//brave/components/brave_component_updater/browser", "//brave/components/brave_extension:generated_resources", "//brave/components/brave_extension:static_resources", + "//brave/components/brave_shields/browser", "//brave/components/brave_wayback_machine:buildflags", "//chrome/browser/extensions", + "//chrome/common", "//components/gcm_driver:gcm_driver", "//components/gcm_driver:gcm_buildflags", "//components/prefs", diff --git a/browser/extensions/api/brave_shields_api.cc b/browser/extensions/api/brave_shields_api.cc index 14777de13cea..b483985bb5e8 100644 --- a/browser/extensions/api/brave_shields_api.cc +++ b/browser/extensions/api/brave_shields_api.cc @@ -10,10 +10,12 @@ #include #include "base/strings/string_number_conversions.h" +#include "brave/browser/brave_browser_process_impl.h" #include "brave/browser/extensions/api/brave_action_api.h" #include "brave/browser/webcompat_reporter/webcompat_reporter_dialog.h" #include "brave/common/extensions/api/brave_shields.h" #include "brave/common/extensions/extension_constants.h" +#include "brave/components/brave_shields/browser/ad_block_service.h" #include "brave/components/brave_shields/browser/brave_shields_p3a.h" #include "brave/components/brave_shields/browser/brave_shields_util.h" #include "brave/components/brave_shields/browser/brave_shields_web_contents_observer.h" @@ -23,6 +25,7 @@ #include "chrome/browser/extensions/chrome_extension_function_details.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_features.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_util.h" @@ -41,6 +44,40 @@ const char kInvalidControlTypeError[] = "Invalid ControlType."; } // namespace + +ExtensionFunction::ResponseAction +BraveShieldsHostnameCosmeticResourcesFunction::Run() { + std::unique_ptr params( + brave_shields::HostnameCosmeticResources::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + base::Optional resources = g_brave_browser_process-> + ad_block_service()->HostnameCosmeticResources(params->hostname); + + if (!resources || !resources->is_dict()) { + return RespondNow(Error( + "Hostname-specific cosmetic resources could not be returned")); + } + auto result_list = std::make_unique(); + + result_list->GetList().push_back(std::move(*resources)); + + return RespondNow(ArgumentList(std::move(result_list))); +} + +ExtensionFunction::ResponseAction BraveShieldsClassIdStylesheetFunction::Run() { + std::unique_ptr params( + brave_shields::ClassIdStylesheet::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + std::string stylesheet = g_brave_browser_process-> + ad_block_service()->ClassIdStylesheet(params->classes, + params->ids, + params->exceptions); + return RespondNow(OneArgument(std::make_unique(stylesheet))); +} + + ExtensionFunction::ResponseAction BraveShieldsAllowScriptsOnceFunction::Run() { std::unique_ptr params( brave_shields::AllowScriptsOnce::Params::Create(*args_)); @@ -117,6 +154,14 @@ BraveShieldsGetBraveShieldsEnabledFunction::Run() { return RespondNow(OneArgument(std::move(result))); } +ExtensionFunction::ResponseAction +BraveShieldsGetCosmeticFilteringEnabledFunction::Run() { + auto result = std::make_unique( + base::FeatureList::IsEnabled(features::kBraveAdblockCosmeticFiltering)); + + return RespondNow(OneArgument(std::move(result))); +} + ExtensionFunction::ResponseAction BraveShieldsSetAdControlTypeFunction::Run() { std::unique_ptr params( brave_shields::SetAdControlType::Params::Create(*args_)); diff --git a/browser/extensions/api/brave_shields_api.h b/browser/extensions/api/brave_shields_api.h index 017dda73446e..21a6adb36a36 100644 --- a/browser/extensions/api/brave_shields_api.h +++ b/browser/extensions/api/brave_shields_api.h @@ -11,6 +11,26 @@ namespace extensions { namespace api { +class BraveShieldsHostnameCosmeticResourcesFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("braveShields.hostnameCosmeticResources", UNKNOWN) + + protected: + ~BraveShieldsHostnameCosmeticResourcesFunction() override {} + + ResponseAction Run() override; +}; + +class BraveShieldsClassIdStylesheetFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("braveShields.classIdStylesheet", UNKNOWN) + + protected: + ~BraveShieldsClassIdStylesheetFunction() override {} + + ResponseAction Run() override; +}; + class BraveShieldsAllowScriptsOnceFunction : public ExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("braveShields.allowScriptsOnce", UNKNOWN) @@ -52,6 +72,18 @@ class BraveShieldsGetBraveShieldsEnabledFunction : public ExtensionFunction { ResponseAction Run() override; }; +class BraveShieldsGetCosmeticFilteringEnabledFunction + : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("braveShields.getCosmeticFilteringEnabled", + UNKNOWN) + + protected: + ~BraveShieldsGetCosmeticFilteringEnabledFunction() override {} + + ResponseAction Run() override; +}; + class BraveShieldsSetAdControlTypeFunction : public ExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("braveShields.setAdControlType", UNKNOWN) diff --git a/chromium_src/chrome/browser/about_flags.cc b/chromium_src/chrome/browser/about_flags.cc index e910bb11f61f..3a6acc4a47d2 100644 --- a/chromium_src/chrome/browser/about_flags.cc +++ b/chromium_src/chrome/browser/about_flags.cc @@ -3,6 +3,12 @@ * 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/. */ +#define BRAVE_FEATURE_ENTRIES \ + {"brave-adblock-cosmetic-filtering", \ + flag_descriptions::kBraveAdblockCosmeticFilteringName, \ + flag_descriptions::kBraveAdblockCosmeticFilteringDescription, kOsAll, \ + FEATURE_VALUE_TYPE(features::kBraveAdblockCosmeticFiltering)}, + #define SetFeatureEntryEnabled SetFeatureEntryEnabled_ChromiumImpl #include "../../../../chrome/browser/about_flags.cc" // NOLINT #include "../../../../components/flags_ui/flags_state.cc" // NOLINT diff --git a/chromium_src/chrome/browser/flag_descriptions.cc b/chromium_src/chrome/browser/flag_descriptions.cc new file mode 100644 index 000000000000..9c63c166915d --- /dev/null +++ b/chromium_src/chrome/browser/flag_descriptions.cc @@ -0,0 +1,12 @@ +/* Copyright (c) 2019 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/. */ + +#include "../../../../chrome/browser/flag_descriptions.cc" + +namespace flag_descriptions { +const char kBraveAdblockCosmeticFilteringName[] = "Enable cosmetic filtering"; +const char kBraveAdblockCosmeticFilteringDescription[] = + "Enable support for cosmetic filtering"; +} diff --git a/chromium_src/chrome/browser/flag_descriptions.h b/chromium_src/chrome/browser/flag_descriptions.h new file mode 100644 index 000000000000..dda099d1930b --- /dev/null +++ b/chromium_src/chrome/browser/flag_descriptions.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2019 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/. */ + +#ifndef BRAVE_CHROMIUM_SRC_CHROME_BROWSER_FLAG_DESCRIPTIONS_H_ +#define BRAVE_CHROMIUM_SRC_CHROME_BROWSER_FLAG_DESCRIPTIONS_H_ + +#include "../../../../chrome/browser/flag_descriptions.h" + +namespace flag_descriptions { +extern const char kBraveAdblockCosmeticFilteringName[]; +extern const char kBraveAdblockCosmeticFilteringDescription[]; +} + +#endif // BRAVE_CHROMIUM_SRC_CHROME_BROWSER_FLAG_DESCRIPTIONS_H_ diff --git a/chromium_src/chrome/common/chrome_features.cc b/chromium_src/chrome/common/chrome_features.cc new file mode 100644 index 000000000000..6b76fbe55068 --- /dev/null +++ b/chromium_src/chrome/common/chrome_features.cc @@ -0,0 +1,12 @@ +/* Copyright (c) 2019 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/. */ + +#include "../../../../chrome/common/chrome_features.cc" + +namespace features { +const base::Feature kBraveAdblockCosmeticFiltering{ + "BraveAdblockCosmeticFiltering", + base::FEATURE_DISABLED_BY_DEFAULT}; +} diff --git a/chromium_src/chrome/common/chrome_features.h b/chromium_src/chrome/common/chrome_features.h new file mode 100644 index 000000000000..b92d0084713a --- /dev/null +++ b/chromium_src/chrome/common/chrome_features.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2019 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/. */ + +#ifndef BRAVE_CHROMIUM_SRC_CHROME_COMMON_CHROME_FEATURES_H_ +#define BRAVE_CHROMIUM_SRC_CHROME_COMMON_CHROME_FEATURES_H_ + +#include "../../../../chrome/common/chrome_features.h" + +namespace features { +COMPONENT_EXPORT(CHROME_FEATURES) +extern const base::Feature kBraveAdblockCosmeticFiltering; +} + +#endif // BRAVE_CHROMIUM_SRC_CHROME_COMMON_CHROME_FEATURES_H_ diff --git a/common/extensions/api/brave_shields.json b/common/extensions/api/brave_shields.json index 2f57e398a9c1..81ea792cb645 100644 --- a/common/extensions/api/brave_shields.json +++ b/common/extensions/api/brave_shields.json @@ -73,6 +73,65 @@ "description": "Notifies the browser about the fact of showing the panel", "parameters": [] }, + { + "name": "hostnameCosmeticResources", + "type": "function", + "description": "Get a cosmetic adblocking stylesheet, generic style exceptions, and script injections specific for the given hostname and domain", + "parameters": [ + { + "name": "hostname", + "type": "string" + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "hostnameSpecificResources", + "type": "object", + "properties": { + "hide_selectors": {"type": "array", "items": {"type": "string"}, "description": "Hostname-specific CSS selectors that should be hidden from the page"}, + "style_selectors": {"type": "object", "additionalProperties": {"type": "array", "items": {"type": "string"}}, "description": "Hostname-specific CSS selectors that should be restyled, with their associated CSS style rules"}, + "exceptions": {"type": "array", "items": {"type": "string"}, "description": "Hostname-specific overrides for generic cosmetic blocking selectors"}, + "injected_script": {"type": "string", "description": "A script to inject as the page is loading"} + } + } + ] + } + ] + }, + { + "name": "classIdStylesheet", + "type": "function", + "description": "Get a stylesheet of generic rules that may apply to the given set of classes and ids without any of the given excepted selectors", + "parameters": [ + { + "name": "classes", + "type": "array", + "items": {"type": "string"} + }, + { + "name": "ids", + "type": "array", + "items": {"type": "string"} + }, + { + "name": "exceptions", + "type": "array", + "items": {"type": "string"} + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "stylesheet", + "type": "string" + } + ] + } + ] + }, { "name": "getBraveShieldsEnabled", "type": "function", @@ -94,6 +153,23 @@ } ] }, + { + "name": "getCosmeticFilteringEnabled", + "type": "function", + "description": "Get whether or not the cosmetic filtering feature flag is enabled", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "enabled", + "type": "boolean" + } + ] + } + ] + }, { "name": "setAdControlType", "type": "function", diff --git a/components/brave_extension/extension/brave_extension/BUILD.gn b/components/brave_extension/extension/brave_extension/BUILD.gn index 3cdad09ed270..2e0e84155c1e 100644 --- a/components/brave_extension/extension/brave_extension/BUILD.gn +++ b/components/brave_extension/extension/brave_extension/BUILD.gn @@ -9,6 +9,7 @@ transpile_web_ui("brave_extension") { ["brave_extension_background", rebase_path("background.ts")], ["content", rebase_path("content.ts")], ["content_dapps", rebase_path("content_dapps.ts")], + ["content_cosmetic", rebase_path("content_cosmetic.ts")], ["webstore", rebase_path("webstore.ts")], ] diff --git a/components/brave_extension/extension/brave_extension/actions/shieldsPanelActions.ts b/components/brave_extension/extension/brave_extension/actions/shieldsPanelActions.ts index b566c3247c9d..2d590928b1b2 100644 --- a/components/brave_extension/extension/brave_extension/actions/shieldsPanelActions.ts +++ b/components/brave_extension/extension/brave_extension/actions/shieldsPanelActions.ts @@ -137,3 +137,28 @@ export const shieldsReady: actions.ShieldsReady = () => { type: types.SHIELDS_READY } } + +export const generateClassIdStylesheet = (tabId: number, classes: string[], ids: string[]) => { + return { + type: types.GENERATE_CLASS_ID_STYLESHEET, + tabId, + classes, + ids + } +} + +export const cosmeticFilterRuleExceptions = (tabId: number, exceptions: string[]) => { + return { + type: types.COSMETIC_FILTER_RULE_EXCEPTIONS, + tabId, + exceptions + } +} + +export const contentScriptsLoaded: actions.ContentScriptsLoaded = (tabId: number, url: string) => { + return { + type: types.CONTENT_SCRIPTS_LOADED, + tabId, + url + } +} diff --git a/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts b/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts index 936e1453efe6..1ee5d0efeeb7 100644 --- a/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts +++ b/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts @@ -2,6 +2,34 @@ * 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/. */ +import shieldsPanelActions from '../actions/shieldsPanelActions' + +const generateCosmeticBlockingStylesheet = (hideSelectors: string[], styleSelectors: any) => { + let stylesheet = '' + if (hideSelectors.length > 0) { + stylesheet += hideSelectors[0] + for (const selector of hideSelectors.slice(1)) { + stylesheet += ',' + selector + } + stylesheet += '{display:none !important;}\n' + } + for (const selector in styleSelectors) { + stylesheet += selector + '{' + styleSelectors[selector] + '\n' + } + + return stylesheet +} + +export const injectClassIdStylesheet = (tabId: number, classes: string[], ids: string[], exceptions: string[]) => { + chrome.braveShields.classIdStylesheet(classes, ids, exceptions, stylesheet => { + chrome.tabs.insertCSS(tabId, { + code: stylesheet, + cssOrigin: 'user', + runAt: 'document_start' + }) + }) +} + export const addSiteCosmeticFilter = async (origin: string, cssfilter: string) => { chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { let storeList = Object.assign({}, storeData.cosmeticFilterList) @@ -22,7 +50,34 @@ export const removeSiteFilter = (origin: string) => { }) } -export const applySiteFilters = (tabId: number, hostname: string) => { +export const applyAdblockCosmeticFilters = (tabId: number, hostname: string) => { + chrome.braveShields.hostnameCosmeticResources(hostname, async (resources) => { + if (chrome.runtime.lastError) { + console.warn('Unable to get cosmetic filter data for the current host') + return + } + + const stylesheet = generateCosmeticBlockingStylesheet(resources.hide_selectors, resources.style_selectors) + if (stylesheet) { + chrome.tabs.insertCSS(tabId, { + code: stylesheet, + cssOrigin: 'user', + runAt: 'document_start' + }) + } + + if (resources.injected_script) { + chrome.tabs.executeScript(tabId, { + code: resources.injected_script, + runAt: 'document_start' + }) + } + + shieldsPanelActions.cosmeticFilterRuleExceptions(tabId, resources.exceptions) + }) +} + +export const applyCSSCosmeticFilters = (tabId: number, hostname: string) => { chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { if (!storeData.cosmeticFilterList) { if (process.env.NODE_ENV === 'shields_development') { diff --git a/components/brave_extension/extension/brave_extension/background/api/shieldsAPI.ts b/components/brave_extension/extension/brave_extension/background/api/shieldsAPI.ts index b39824165496..22499e17e1f9 100644 --- a/components/brave_extension/extension/brave_extension/background/api/shieldsAPI.ts +++ b/components/brave_extension/extension/brave_extension/background/api/shieldsAPI.ts @@ -27,7 +27,8 @@ export const getShieldSettingsForTabData = (tabData?: chrome.tabs.Tab) => { chrome.braveShields.getHTTPSEverywhereEnabledAsync(tabData.url), chrome.braveShields.getNoScriptControlTypeAsync(tabData.url), chrome.braveShields.getFingerprintingControlTypeAsync(tabData.url), - chrome.braveShields.getCookieControlTypeAsync(tabData.url) + chrome.braveShields.getCookieControlTypeAsync(tabData.url), + chrome.braveShields.getCosmeticFilteringEnabledAsync() ]).then((details) => { return { url: url.href, @@ -40,7 +41,8 @@ export const getShieldSettingsForTabData = (tabData?: chrome.tabs.Tab) => { httpUpgradableResources: details[2] ? 'block' : 'allow', javascript: details[3], fingerprinting: details[4], - cookies: details[5] + cookies: details[5], + cosmeticBlocking: details[6] } }).catch(() => { return { @@ -49,6 +51,7 @@ export const getShieldSettingsForTabData = (tabData?: chrome.tabs.Tab) => { hostname, id: tabData.id, braveShields: 'block', + cosmeticBlocking: 0, ads: 0, trackers: 0, httpUpgradableResources: 0, diff --git a/components/brave_extension/extension/brave_extension/background/events/cosmeticFilterEvents.ts b/components/brave_extension/extension/brave_extension/background/events/cosmeticFilterEvents.ts index 9e92d5a84b10..99b92f25eed9 100644 --- a/components/brave_extension/extension/brave_extension/background/events/cosmeticFilterEvents.ts +++ b/components/brave_extension/extension/brave_extension/background/events/cosmeticFilterEvents.ts @@ -4,6 +4,7 @@ import { removeSiteFilter, removeAllFilters } from '../api/cosmeticFilterAPI' +import shieldsPanelActions from '../actions/shieldsPanelActions' export let rule = { host: '', @@ -49,6 +50,33 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { rule.host = msg.baseURI break } + case 'classIdStylesheet': { + const tab = sender.tab + if (tab === undefined) { + break + } + const tabId = tab.id + if (tabId === undefined) { + break + } + shieldsPanelActions.generateClassIdStylesheet(tabId, msg.classes, msg.ids) + break + } + case 'contentScriptsLoaded': { + const tab = sender.tab + if (tab === undefined) { + break + } + const tabId = tab.id + if (tabId === undefined) { + break + } + const url = tab.url + if (url === undefined) { + break + } + shieldsPanelActions.contentScriptsLoaded(tabId, url) + } } }) diff --git a/components/brave_extension/extension/brave_extension/background/reducers/shieldsPanelReducer.ts b/components/brave_extension/extension/brave_extension/background/reducers/shieldsPanelReducer.ts index c0269e98a179..8db43e0c2cfa 100644 --- a/components/brave_extension/extension/brave_extension/background/reducers/shieldsPanelReducer.ts +++ b/components/brave_extension/extension/brave_extension/background/reducers/shieldsPanelReducer.ts @@ -33,7 +33,11 @@ import { reportBrokenSite } from '../api/shieldsAPI' import { reloadTab } from '../api/tabsAPI' -import { applySiteFilters } from '../api/cosmeticFilterAPI' +import { + injectClassIdStylesheet, + applyAdblockCosmeticFilters, + applyCSSCosmeticFilters +} from '../api/cosmeticFilterAPI' // Helpers import { getAllowedScriptsOrigins } from '../../helpers/noScriptUtils' @@ -58,7 +62,7 @@ export default function shieldsPanelReducer ( state = shieldsPanelState.resetBlockingResources(state, action.tabId) state = noScriptState.resetNoScriptInfo(state, action.tabId, new window.URL(action.url).origin) } - applySiteFilters(action.tabId, getHostname(action.url)) + applyCSSCosmeticFilters(action.tabId, getHostname(action.url)) break } case windowTypes.WINDOW_REMOVED: { @@ -105,6 +109,9 @@ export default function shieldsPanelReducer ( case shieldsPanelTypes.SHIELDS_PANEL_DATA_UPDATED: { state = shieldsPanelState.updateTabShieldsData(state, action.details.id, action.details) shieldsPanelState.updateShieldsIcon(state) + if (chrome.test && shieldsPanelState.getActiveTabData(state)) { + chrome.test.sendMessage('brave-extension-shields-data-ready') + } break } case shieldsPanelTypes.SHIELDS_TOGGLED: { @@ -340,6 +347,47 @@ export default function shieldsPanelReducer ( }) break } + case shieldsPanelTypes.GENERATE_CLASS_ID_STYLESHEET: { + const tabData = state.tabs[action.tabId] + if (!tabData) { + console.error('Active tab not found') + break + } + const exceptions = tabData.cosmeticFilters.ruleExceptions + + // setTimeout is used to prevent injectClassIdStylesheet from calling + // another Redux function immediately + setTimeout(() => injectClassIdStylesheet(action.tabId, action.classes, action.ids, exceptions), 0) + break + } + case shieldsPanelTypes.COSMETIC_FILTER_RULE_EXCEPTIONS: { + const tabData = state.tabs[action.tabId] + if (!tabData) { + console.error('Active tab not found') + break + } + state = shieldsPanelState.saveCosmeticFilterRuleExceptions(state, action.tabId, action.exceptions) + chrome.tabs.sendMessage(action.tabId, { + type: 'cosmeticFilterGenericExceptions' + }) + break + } + case shieldsPanelTypes.CONTENT_SCRIPTS_LOADED: { + const tabData = state.tabs[action.tabId] + if (!tabData) { + console.error('Active tab not found') + break + } + const cosmeticBlockingEnabled = tabData.cosmeticBlocking + chrome.braveShields.getBraveShieldsEnabledAsync(action.url) + .then((braveShieldsEnabled: boolean) => { + const doCosmeticBlocking = braveShieldsEnabled && cosmeticBlockingEnabled + if (doCosmeticBlocking) { + applyAdblockCosmeticFilters(action.tabId, getHostname(action.url)) + } + }) + break + } } if (!areObjectsEqual(state.persistentData, initialPersistentData)) { diff --git a/components/brave_extension/extension/brave_extension/constants/shieldsPanelTypes.ts b/components/brave_extension/extension/brave_extension/constants/shieldsPanelTypes.ts index 0b1ee008ef0d..faea8d578819 100644 --- a/components/brave_extension/extension/brave_extension/constants/shieldsPanelTypes.ts +++ b/components/brave_extension/extension/brave_extension/constants/shieldsPanelTypes.ts @@ -20,3 +20,6 @@ export const SET_FINAL_SCRIPTS_BLOCKED_ONCE_STATE = 'SET_FINAL_SCRIPTS_BLOCKED_O export const SET_ADVANCED_VIEW_FIRST_ACCESS = 'SET_ADVANCED_VIEW_FIRST_ACCESS' export const TOGGLE_ADVANCED_VIEW = 'TOGGLE_ADVANCED_VIEW' export const SHIELDS_READY = 'SHIELDS_READY' +export const GENERATE_CLASS_ID_STYLESHEET = 'GENERATE_CLASS_ID_STYLESHEET' +export const COSMETIC_FILTER_RULE_EXCEPTIONS = 'COSMETIC_FILTER_RULE_EXCEPTIONS' +export const CONTENT_SCRIPTS_LOADED = 'CONTENT_SCRIPTS_LOADED' diff --git a/components/brave_extension/extension/brave_extension/content.ts b/components/brave_extension/extension/brave_extension/content.ts index a1c3d55d2388..7256e7238c07 100644 --- a/components/brave_extension/extension/brave_extension/content.ts +++ b/components/brave_extension/extension/brave_extension/content.ts @@ -1,3 +1,13 @@ +// Notify the background script as soon as the content script has loaded. +// chrome.tabs.insertCSS may sometimes fail to inject CSS in a newly navigated +// page when using the chrome.webNavigation API. +// See: https://bugs.chromium.org/p/chromium/issues/detail?id=331654#c15 +// The RenderView should always be ready when the content script begins, so +// this message is used to trigger CSS insertion instead. +chrome.runtime.sendMessage({ + type: 'contentScriptsLoaded' +}) + const unique = require('unique-selector').default let target: EventTarget | null @@ -20,6 +30,7 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { switch (action) { case 'getTargetSelector': { sendResponse(unique(target)) + break } } }) diff --git a/components/brave_extension/extension/brave_extension/content_cosmetic.ts b/components/brave_extension/extension/brave_extension/content_cosmetic.ts new file mode 100644 index 000000000000..9e95a141cba8 --- /dev/null +++ b/components/brave_extension/extension/brave_extension/content_cosmetic.ts @@ -0,0 +1,80 @@ +const queriedIds = new Set() +const queriedClasses = new Set() +const regexWhitespace = /\s/ + +const getClassesAndIds = function (addedNodes: Element[]) { + const ids = [] + const classes = [] + + for (const node of addedNodes) { + let nodeId = node.id + if (nodeId && nodeId.length !== 0) { + nodeId = nodeId.trim() + if (!queriedIds.has(nodeId) && nodeId.length !== 0) { + ids.push(nodeId) + queriedIds.add(nodeId) + } + } + let nodeClass = node.className + if (nodeClass && nodeClass.length !== 0 && !regexWhitespace.test(nodeClass)) { + if (!queriedClasses.has(nodeClass)) { + classes.push(nodeClass) + queriedClasses.add(nodeClass) + } + } else { + let nodeClasses = node.classList + if (nodeClasses) { + let j = nodeClasses.length + while (j--) { + const nodeClassJ = nodeClasses[j] + if (queriedClasses.has(nodeClassJ) === false) { + classes.push(nodeClassJ) + queriedClasses.add(nodeClassJ) + } + } + } + } + } + return { classes, ids } +} + +const handleNewNodes = (newNodes: Element[]) => { + const { classes, ids } = getClassesAndIds(newNodes) + chrome.runtime.sendMessage({ + type: 'classIdStylesheet', + classes, + ids + }) +} + +function applyCosmeticFilterMutationObserver () { + let targetNode = document.documentElement + let observer = new MutationObserver(mutations => { + const nodeList: Element[] = [] + for (const mutation of mutations) { + for (let nodeIndex = 0; nodeIndex < mutation.addedNodes.length; nodeIndex++) { + nodeList.push(mutation.addedNodes[nodeIndex] as Element) + } + } + handleNewNodes(nodeList) + }) + let observerConfig = { + childList: true, + subtree: true + } + observer.observe(targetNode, observerConfig) +} + +chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { + const action = typeof msg === 'string' ? msg : msg.type + switch (action) { + case 'cosmeticFilterGenericExceptions': { + let allNodes = Array.from(document.querySelectorAll('[id],[class]')) + handleNewNodes(allNodes) + applyCosmeticFilterMutationObserver() + + sendResponse(null) + break + } + } +}) diff --git a/components/brave_extension/extension/brave_extension/manifest.json b/components/brave_extension/extension/brave_extension/manifest.json index d651fea23591..021b4ad92b1c 100644 --- a/components/brave_extension/extension/brave_extension/manifest.json +++ b/components/brave_extension/extension/brave_extension/manifest.json @@ -28,7 +28,8 @@ "https://*/*" ], "js": [ - "out/content.bundle.js" + "out/content.bundle.js", + "out/content_cosmetic.bundle.js" ], "run_at": "document_start", "all_frames": true diff --git a/components/brave_extension/extension/brave_extension/notes.md b/components/brave_extension/extension/brave_extension/notes.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/components/brave_extension/extension/brave_extension/state/shieldsPanelState.ts b/components/brave_extension/extension/brave_extension/state/shieldsPanelState.ts index 7ffedd1f8f16..aa1ef65323bb 100644 --- a/components/brave_extension/extension/brave_extension/state/shieldsPanelState.ts +++ b/components/brave_extension/extension/brave_extension/state/shieldsPanelState.ts @@ -112,6 +112,12 @@ export const updateResourceBlocked: shieldState.UpdateResourceBlocked = (state, return { ...state, tabs } } +export const saveCosmeticFilterRuleExceptions: shieldState.SaveCosmeticFilterRuleExceptions = (state, tabId, exceptions) => { + const tabs: shieldState.Tabs = { ...state.tabs } + tabs[tabId] = { ...tabs[tabId], ...{ cosmeticFilters: { ...tabs[tabId].cosmeticFilters, ruleExceptions: exceptions } } } + return { ...state, tabs } +} + export const resetBlockingStats: shieldState.ResetBlockingStats = (state, tabId) => { const tabs: shieldState.Tabs = { ...state.tabs } tabs[tabId] = { ...tabs[tabId], ...{ adsBlocked: 0, trackersBlocked: 0, httpsRedirected: 0, javascriptBlocked: 0, fingerprintingBlocked: 0 } } diff --git a/components/brave_extension/extension/brave_extension/types/actions/shieldsPanelActions.ts b/components/brave_extension/extension/brave_extension/types/actions/shieldsPanelActions.ts index ae808bae4b13..3aa6f3a29e2d 100644 --- a/components/brave_extension/extension/brave_extension/types/actions/shieldsPanelActions.ts +++ b/components/brave_extension/extension/brave_extension/types/actions/shieldsPanelActions.ts @@ -7,6 +7,7 @@ import { BlockTypes, BlockOptions, BlockFPOptions, BlockJSOptions, BlockCookiesO export interface ShieldDetails { id: number + cosmeticBlocking: boolean ads: BlockOptions trackers: BlockOptions httpUpgradableResources: BlockOptions @@ -172,6 +173,37 @@ export interface ShieldsReady { (): ShieldsReadyReturn } +interface GenerateClassIdStylesheetReturn { + type: types.GENERATE_CLASS_ID_STYLESHEET, + tabId: number, + classes: string[], + ids: string[] +} + +export interface GenerateClassIdStylesheet { + (tabId: number, classes: string[], ids: string[]): GenerateClassIdStylesheetReturn +} + +interface CosmeticFilterRuleExceptionsReturn { + type: types.COSMETIC_FILTER_RULE_EXCEPTIONS, + tabId: number, + exceptions: string[] +} + +export interface CosmeticFilterRuleExceptions { + (tabId: number, exceptions: string[]): CosmeticFilterRuleExceptionsReturn +} + +interface ContentScriptsLoadedReturn { + type: types.CONTENT_SCRIPTS_LOADED, + tabId: number, + url: string, +} + +export interface ContentScriptsLoaded { + (tabId: number, url: string): ContentScriptsLoadedReturn +} + export type shieldPanelActions = ShieldsPanelDataUpdatedReturn | ShieldsToggledReturn | @@ -189,4 +221,7 @@ export type shieldPanelActions = SetAllScriptsBlockedCurrentStateReturn | SetFinalScriptsBlockedStateReturn | SetAdvancedViewFirstAccessReturn | - ShieldsReadyReturn + ShieldsReadyReturn | + GenerateClassIdStylesheetReturn | + CosmeticFilterRuleExceptionsReturn | + ContentScriptsLoadedReturn diff --git a/components/brave_extension/extension/brave_extension/types/adblock/adblockTypes.ts b/components/brave_extension/extension/brave_extension/types/adblock/adblockTypes.ts index 3503d66c956e..d8ab3dca6cf0 100644 --- a/components/brave_extension/extension/brave_extension/types/adblock/adblockTypes.ts +++ b/components/brave_extension/extension/brave_extension/types/adblock/adblockTypes.ts @@ -6,3 +6,7 @@ export type BlockTypes = 'ads' | 'trackers' | 'httpUpgradableResources' | 'javas export type BlockOptions = 'allow' | 'block' export type BlockFPOptions = 'allow' | 'block' | 'block_third_party' export type BlockCookiesOptions = 'allow' | 'block' | 'block_third_party' + +export interface CosmeticFilteringState { + ruleExceptions: Array +} diff --git a/components/brave_extension/extension/brave_extension/types/constants/shieldsPanelTypes.ts b/components/brave_extension/extension/brave_extension/types/constants/shieldsPanelTypes.ts index 646d2f97c11d..05c2568a3cbb 100644 --- a/components/brave_extension/extension/brave_extension/types/constants/shieldsPanelTypes.ts +++ b/components/brave_extension/extension/brave_extension/types/constants/shieldsPanelTypes.ts @@ -22,3 +22,6 @@ export type SET_FINAL_SCRIPTS_BLOCKED_ONCE_STATE = typeof types.SET_FINAL_SCRIPT export type SET_ADVANCED_VIEW_FIRST_ACCESS = typeof types.SET_ADVANCED_VIEW_FIRST_ACCESS export type TOGGLE_ADVANCED_VIEW = typeof types.TOGGLE_ADVANCED_VIEW export type SHIELDS_READY = typeof types.SHIELDS_READY +export type GENERATE_CLASS_ID_STYLESHEET = typeof types.GENERATE_CLASS_ID_STYLESHEET +export type COSMETIC_FILTER_RULE_EXCEPTIONS = typeof types.COSMETIC_FILTER_RULE_EXCEPTIONS +export type CONTENT_SCRIPTS_LOADED = typeof types.CONTENT_SCRIPTS_LOADED diff --git a/components/brave_extension/extension/brave_extension/types/state/shieldsPannelState.ts b/components/brave_extension/extension/brave_extension/types/state/shieldsPannelState.ts index d7e5f2012b28..2738c15456b2 100644 --- a/components/brave_extension/extension/brave_extension/types/state/shieldsPannelState.ts +++ b/components/brave_extension/extension/brave_extension/types/state/shieldsPannelState.ts @@ -3,9 +3,11 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ import { BlockOptions, BlockTypes, BlockFPOptions, BlockCookiesOptions } from '../other/blockTypes' +import { CosmeticFilteringState } from '../adblock/adblockTypes' import { NoScriptInfo } from '../other/noScriptInfo' export interface Tab { + cosmeticBlocking: boolean ads: BlockOptions adsBlocked: number controlsOpen: boolean @@ -28,6 +30,7 @@ export interface Tab { trackersBlockedResources: Array httpsRedirectedResources: Array fingerprintingBlockedResources: Array + cosmeticFilters: CosmeticFilteringState } export interface Tabs { @@ -85,6 +88,10 @@ export interface UpdateResourceBlocked { (state: State, tabId: number, blockType: BlockTypes, subresource: string): State } +export interface SaveCosmeticFilterRuleExceptions { + (state: State, tabId: number, exceptions: Array): State +} + export interface ResetBlockingStats { (state: State, tabId: number): State } diff --git a/components/brave_shields/browser/BUILD.gn b/components/brave_shields/browser/BUILD.gn index b29c3f3f2dcf..ae923b45a0ed 100644 --- a/components/brave_shields/browser/BUILD.gn +++ b/components/brave_shields/browser/BUILD.gn @@ -55,6 +55,7 @@ source_set("browser") { } deps = [ + "//base", "//brave/components/brave_component_updater/browser", "//brave/components/content_settings/core/browser", "//brave/content:common", diff --git a/components/brave_shields/browser/ad_block_base_service.cc b/components/brave_shields/browser/ad_block_base_service.cc index eb944d382fa3..45bc9685444a 100644 --- a/components/brave_shields/browser/ad_block_base_service.cc +++ b/components/brave_shields/browser/ad_block_base_service.cc @@ -12,6 +12,7 @@ #include "base/bind.h" #include "base/files/file_path.h" +#include "base/json/json_reader.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/strings/utf_string_conversions.h" @@ -195,6 +196,19 @@ bool AdBlockBaseService::TagExists(const std::string& tag) { return std::find(tags_.begin(), tags_.end(), tag) != tags_.end(); } +base::Optional AdBlockBaseService::HostnameCosmeticResources( + const std::string& hostname) { + return base::JSONReader::Read( + this->ad_block_client_->hostnameCosmeticResources(hostname)); +} + +std::string AdBlockBaseService::ClassIdStylesheet( + const std::vector& classes, + const std::vector& ids, + const std::vector& exceptions) { + return this->ad_block_client_->classIdStylesheet(classes, ids, exceptions); +} + void AdBlockBaseService::GetDATFileData(const base::FilePath& dat_file_path) { base::PostTaskAndReplyWithResult( FROM_HERE, {base::ThreadPool(), base::MayBlock()}, diff --git a/components/brave_shields/browser/ad_block_base_service.h b/components/brave_shields/browser/ad_block_base_service.h index a8d5cbb6b8fb..50f1469671f1 100644 --- a/components/brave_shields/browser/ad_block_base_service.h +++ b/components/brave_shields/browser/ad_block_base_service.h @@ -16,6 +16,7 @@ #include "base/files/file_path.h" #include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" +#include "base/values.h" #include "brave/components/brave_shields/browser/base_brave_shields_service.h" #include "brave/components/brave_component_updater/browser/dat_file_util.h" #include "content/public/common/resource_type.h" @@ -46,6 +47,13 @@ class AdBlockBaseService : public BaseBraveShieldsService { void EnableTag(const std::string& tag, bool enabled); bool TagExists(const std::string& tag); + base::Optional HostnameCosmeticResources( + const std::string& hostname); + std::string ClassIdStylesheet( + const std::vector& classes, + const std::vector& ids, + const std::vector& exceptions); + protected: friend class ::AdBlockServiceTest; bool Init() override; diff --git a/components/brave_shields/browser/ad_block_service_browsertest.cc b/components/brave_shields/browser/ad_block_service_browsertest.cc index 149fbafd8c07..d794ad7f28c6 100644 --- a/components/brave_shields/browser/ad_block_service_browsertest.cc +++ b/components/brave_shields/browser/ad_block_service_browsertest.cc @@ -19,11 +19,13 @@ #include "brave/vendor/adblock_rust_ffi/src/wrapper.hpp" #include "chrome/browser/extensions/extension_browsertest.h" #include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_features.h" #include "chrome/test/base/ui_test_utils.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/test/browser_test_utils.h" +#include "extensions/test/extension_test_message_listener.h" #include "net/dns/mock_host_resolver.h" using content::BrowserThread; @@ -218,6 +220,15 @@ class AdBlockServiceTest : public ExtensionBrowserTest { base::CreateSingleThreadTaskRunner({BrowserThread::IO}).get())); ASSERT_TRUE(io_helper->Run()); } + + void WaitForBraveExtensionShieldsDataReady() { + // Sometimes, the page can start loading before the Shields panel has + // received information about the window and tab it's loaded in. + ExtensionTestMessageListener extension_listener( + "brave-extension-shields-data-ready", + false); + ASSERT_TRUE(extension_listener.WaitUntilSatisfied()); + } }; // Load a page with an ad image, and make sure it is blocked. @@ -774,3 +785,172 @@ IN_PROC_BROWSER_TEST_F(AdBlockServiceTest, RedirectRulesAreRespected) { EXPECT_TRUE(as_expected); EXPECT_EQ(browser()->profile()->GetPrefs()->GetUint64(kAdsBlocked), 1ULL); } + +class CosmeticFilteringEnabledTest : public AdBlockServiceTest { + public: + CosmeticFilteringEnabledTest() { + feature_list_.InitAndEnableFeature( + features::kBraveAdblockCosmeticFiltering); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +// Ensure no cosmetic filtering occurs when the feature flag has not been +// enabled +IN_PROC_BROWSER_TEST_F(AdBlockServiceTest, CosmeticFilteringSimple) { + UpdateAdBlockInstanceWithRules( + "b.com###ad-banner\n" + "##.ad"); + + WaitForBraveExtensionShieldsDataReady(); + + GURL tab_url = embedded_test_server()->GetURL("b.com", + "/cosmetic_filtering.html"); + ui_test_utils::NavigateToURL(browser(), tab_url); + + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + bool as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('#ad-banner', 'display', 'block')", + &as_expected)); + EXPECT_TRUE(as_expected); + + as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('.ad-banner', 'display', 'block')", + &as_expected)); + EXPECT_TRUE(as_expected); + + as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('.ad', 'display', 'block')", + &as_expected)); + EXPECT_TRUE(as_expected); +} + +// Test simple cosmetic filtering +IN_PROC_BROWSER_TEST_F(CosmeticFilteringEnabledTest, CosmeticFilteringSimple) { + UpdateAdBlockInstanceWithRules( + "b.com###ad-banner\n" + "##.ad"); + + WaitForBraveExtensionShieldsDataReady(); + + GURL tab_url = embedded_test_server()->GetURL("b.com", + "/cosmetic_filtering.html"); + ui_test_utils::NavigateToURL(browser(), tab_url); + + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + bool as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('#ad-banner', 'display', 'none')", + &as_expected)); + EXPECT_TRUE(as_expected); + + as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('.ad-banner', 'display', 'block')", + &as_expected)); + EXPECT_TRUE(as_expected); + + as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('.ad', 'display', 'none')", + &as_expected)); + EXPECT_TRUE(as_expected); +} + +// Test cosmetic filtering on elements added dynamically +IN_PROC_BROWSER_TEST_F(CosmeticFilteringEnabledTest, CosmeticFilteringDynamic) { + UpdateAdBlockInstanceWithRules("##.blockme"); + + WaitForBraveExtensionShieldsDataReady(); + + GURL tab_url = embedded_test_server()->GetURL("b.com", + "/cosmetic_filtering.html"); + ui_test_utils::NavigateToURL(browser(), tab_url); + + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + bool as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "addElementsDynamically();\n" + "checkSelector('.blockme', 'display', 'none')", + &as_expected)); + EXPECT_TRUE(as_expected); + + as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('.dontblockme', 'display', 'block')", + &as_expected)); + EXPECT_TRUE(as_expected); +} + +// Test custom style rules +IN_PROC_BROWSER_TEST_F(CosmeticFilteringEnabledTest, + CosmeticFilteringCustomStyle) { + UpdateAdBlockInstanceWithRules("b.com##.ad:style(padding-bottom: 10px)"); + + WaitForBraveExtensionShieldsDataReady(); + + GURL tab_url = embedded_test_server()->GetURL("b.com", + "/cosmetic_filtering.html"); + ui_test_utils::NavigateToURL(browser(), tab_url); + + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + bool as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('.ad', 'padding-bottom', '10px')", + &as_expected)); + EXPECT_TRUE(as_expected); +} + +// Test rules overridden by hostname-specific exception rules +IN_PROC_BROWSER_TEST_F(CosmeticFilteringEnabledTest, CosmeticFilteringUnhide) { + UpdateAdBlockInstanceWithRules( + "##.ad\n" + "b.com#@#.ad\n" + "###ad-banner\n" + "a.com#@##ad-banner"); + + WaitForBraveExtensionShieldsDataReady(); + + GURL tab_url = embedded_test_server()->GetURL("b.com", + "/cosmetic_filtering.html"); + ui_test_utils::NavigateToURL(browser(), tab_url); + + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + bool as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('.ad', 'display', 'block')", + &as_expected)); + EXPECT_TRUE(as_expected); + + as_expected = false; + ASSERT_TRUE(ExecuteScriptAndExtractBool( + contents, + "checkSelector('#ad-banner', 'display', 'none')", + &as_expected)); + EXPECT_TRUE(as_expected); +} diff --git a/components/definitions/chromel.d.ts b/components/definitions/chromel.d.ts index 79c70ed3d9b9..839f207b6454 100644 --- a/components/definitions/chromel.d.ts +++ b/components/definitions/chromel.d.ts @@ -199,6 +199,7 @@ declare namespace chrome.braveShields { const allowScriptsOnce: any const setBraveShieldsEnabledAsync: any const getBraveShieldsEnabledAsync: any + const getCosmeticFilteringEnabledAsync: any const setAdControlTypeAsync: any const getAdControlTypeAsync: any const setCookieControlTypeAsync: any @@ -212,6 +213,15 @@ declare namespace chrome.braveShields { const onShieldsPanelShown: any const reportBrokenSite: any + interface HostnameSpecificResources { + hide_selectors: string[] + style_selectors: any + exceptions: string[] + injected_script: string + } + const hostnameCosmeticResources: (hostname: string, callback: (resources: HostnameSpecificResources) => void) => void + const classIdStylesheet: (classes: string[], ids: string[], exceptions: string[], callback: (stylesheet: string) => void) => void + type BraveShieldsViewPreferences = { showAdvancedView: boolean } diff --git a/components/test/brave_extension/background/api/cosmeticFilterAPI_test.ts b/components/test/brave_extension/background/api/cosmeticFilterAPI_test.ts index 9d9bfdeeaa21..80edf765018b 100644 --- a/components/test/brave_extension/background/api/cosmeticFilterAPI_test.ts +++ b/components/test/brave_extension/background/api/cosmeticFilterAPI_test.ts @@ -177,7 +177,7 @@ describe('cosmeticFilter API', () => { }) }) }) - describe('applySiteFilters', () => { + describe('applyCSSCosmeticFilters', () => { const filter = '#cssFilter' const filter2 = '#cssFilter2' @@ -206,7 +206,7 @@ describe('cosmeticFilter API', () => { 'brave.com': [filter] } }) - cosmeticFilterAPI.applySiteFilters(1, 'brave.com') + cosmeticFilterAPI.applyCSSCosmeticFilters(1, 'brave.com') expect(insertCSSStub.getCall(0).args[0]).toEqual(1) expect(insertCSSStub.getCall(0).args[1]).toEqual({ code: `${filter} {display: none !important;}`, @@ -220,7 +220,7 @@ describe('cosmeticFilter API', () => { 'brave.com': [filter, filter2] } }) - cosmeticFilterAPI.applySiteFilters(1, 'brave.com') + cosmeticFilterAPI.applyCSSCosmeticFilters(1, 'brave.com') expect(insertCSSStub.getCall(0).args[0]).toEqual(1) expect(insertCSSStub.getCall(0).args[1]).toEqual({ code: `${filter } {display: none !important;}`, @@ -239,7 +239,7 @@ describe('cosmeticFilter API', () => { getStorageStub.yields({ cosmeticFilterList: {} }) - cosmeticFilterAPI.applySiteFilters(1, 'brave.com') + cosmeticFilterAPI.applyCSSCosmeticFilters(1, 'brave.com') expect(insertCSSStub.called).toBe(false) }) it('doesn\'t apply filters if storage is explicitly undefined', () => { @@ -248,7 +248,7 @@ describe('cosmeticFilter API', () => { 'brave.com': undefined } }) - cosmeticFilterAPI.applySiteFilters(1, 'brave.com') + cosmeticFilterAPI.applyCSSCosmeticFilters(1, 'brave.com') expect(insertCSSStub.called).toBe(false) }) }) diff --git a/components/test/brave_extension/background/api/shieldsAPI_test.ts b/components/test/brave_extension/background/api/shieldsAPI_test.ts index 88cf7b8ed2ac..1dace3e64ec6 100644 --- a/components/test/brave_extension/background/api/shieldsAPI_test.ts +++ b/components/test/brave_extension/background/api/shieldsAPI_test.ts @@ -38,6 +38,7 @@ describe('Shields API', () => { origin: 'https://www.brave.com', hostname: 'www.brave.com', braveShields: 'block', + cosmeticBlocking: true, ads: 'block', trackers: 'block', httpUpgradableResources: 'block', @@ -94,6 +95,7 @@ describe('Shields API', () => { }) it('resolves and calls requestShieldPanelData', (cb) => { const details: ShieldDetails = { + cosmeticBlocking: true, ads: 'block', trackers: 'block', httpUpgradableResources: 'block', diff --git a/components/test/testData.ts b/components/test/testData.ts index 9ca1cc7307fe..f1cfd027cd9c 100644 --- a/components/test/testData.ts +++ b/components/test/testData.ts @@ -217,6 +217,9 @@ export const getMockChrome = () => { getBraveShieldsEnabledAsync: function (url: string) { return Promise.resolve(false) }, + getCosmeticFilteringEnabledAsync: function (url: string) { + return Promise.resolve(true) + }, getAdControlTypeAsync: function (url: string) { return Promise.resolve('block') }, diff --git a/patches/chrome-browser-about_flags.cc.patch b/patches/chrome-browser-about_flags.cc.patch index 58e60ac172d2..ad02de37cff0 100644 --- a/patches/chrome-browser-about_flags.cc.patch +++ b/patches/chrome-browser-about_flags.cc.patch @@ -21,3 +21,11 @@ index 656fd33db3f6553a409351701c9839725452df59..0a164040f1a8d76a5eeee517644af14f const FeatureEntry::Choice kPassiveListenersChoices[] = { {flags_ui::kGenericExperimentChoiceDefault, "", ""}, +@@ -4688,6 +4688,7 @@ const FeatureEntry kFeatureEntries[] = { + // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag + // Histograms" in tools/metrics/histograms/README.md (run the + // AboutFlagsHistogramTest unit test to verify this process). ++ BRAVE_FEATURE_ENTRIES + }; + + class FlagsStateSingleton { diff --git a/test/data/cosmetic_filtering.html b/test/data/cosmetic_filtering.html new file mode 100644 index 000000000000..5805241e1466 --- /dev/null +++ b/test/data/cosmetic_filtering.html @@ -0,0 +1,41 @@ + + + + + +
+
+
+
+
+
+ +