diff --git a/browser/extensions/api/brave_shields_api.cc b/browser/extensions/api/brave_shields_api.cc index b979bcef50ef..23450941b8d4 100644 --- a/browser/extensions/api/brave_shields_api.cc +++ b/browser/extensions/api/brave_shields_api.cc @@ -19,6 +19,10 @@ #include "chrome/browser/profiles/profile.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_util.h" +#include "brave/components/brave_shields/browser/ad_block_base_service.h" +#include "brave/components/brave_shields/browser/ad_block_service.h" +#include "brave/browser/brave_browser_process_impl.h" +#include "brave/vendor/adblock_rust_ffi/src/wrapper.hpp" using brave_shields::BraveShieldsWebContentsObserver; using brave_shields::ControlType; @@ -35,6 +39,39 @@ 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()); + + adblock::HostnameResources resources = g_brave_browser_process->ad_block_service()->hostnameCosmeticResources(params->hostname); + + auto result_list = std::make_unique(); + + result_list->GetList().push_back(base::Value(resources.stylesheet)); + + base::Value exceptions(base::Value::Type::LIST); + for(auto i = resources.exceptions.begin(); i != resources.exceptions.end(); i++) { + exceptions.GetList().push_back(base::Value(*i)); + } + result_list->GetList().push_back(std::move(exceptions)); + + result_list->GetList().push_back(base::Value(resources.injected_script)); + + 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_)); @@ -92,6 +129,46 @@ BraveShieldsGetBraveShieldsEnabledFunction::Run() { return RespondNow(OneArgument(std::move(result))); } +ExtensionFunction::ResponseAction BraveShieldsSetCosmeticFilteredElementsFunction::Run() { + std::unique_ptr params( + brave_shields::SetCosmeticFilteredElements::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + const GURL url(params->url); + // we don't allow setting defaults from the extension + if (url.is_empty() || !url.is_valid()) { + return RespondNow(Error(kInvalidUrlError, params->url)); + } + + auto control_type = ControlTypeFromString(params->control_type); + if (control_type == ControlType::INVALID) { + return RespondNow(Error(kInvalidControlTypeError, params->control_type)); + } + + Profile* profile = Profile::FromBrowserContext(browser_context()); + ::brave_shields::SetCosmeticFilteredElements(profile, control_type, url); + + return RespondNow(NoArguments()); +} + +ExtensionFunction::ResponseAction BraveShieldsGetCosmeticFilteredElementsFunction::Run() { + std::unique_ptr params( + brave_shields::GetCosmeticFilteredElements::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + const GURL url(params->url); + // we don't allow getting defaults from the extension + if (url.is_empty() || !url.is_valid()) { + return RespondNow(Error(kInvalidUrlError, params->url)); + } + + Profile* profile = Profile::FromBrowserContext(browser_context()); + auto type = ::brave_shields::GetCosmeticFilteredElements(profile, url); + auto result = std::make_unique(ControlTypeToString(type)); + + 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 59380d2de9eb..aad9c8c78605 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 UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("braveShields.hostnameCosmeticResources", UNKNOWN) + + protected: + ~BraveShieldsHostnameCosmeticResourcesFunction() override {} + + ResponseAction Run() override; +}; + +class BraveShieldsClassIdStylesheetFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("braveShields.classIdStylesheet", UNKNOWN) + + protected: + ~BraveShieldsClassIdStylesheetFunction() override {} + + ResponseAction Run() override; +}; + class BraveShieldsAllowScriptsOnceFunction : public UIThreadExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("braveShields.allowScriptsOnce", UNKNOWN) @@ -43,6 +63,26 @@ class BraveShieldsGetBraveShieldsEnabledFunction ResponseAction Run() override; }; +class BraveShieldsSetCosmeticFilteredElementsFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("braveShields.setCosmeticFilteredElements", UNKNOWN) + + protected: + ~BraveShieldsSetCosmeticFilteredElementsFunction() override {} + + ResponseAction Run() override; +}; + +class BraveShieldsGetCosmeticFilteredElementsFunction : public UIThreadExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("braveShields.getCosmeticFilteredElements", UNKNOWN) + + protected: + ~BraveShieldsGetCosmeticFilteredElementsFunction() override {} + + ResponseAction Run() override; +}; + class BraveShieldsSetAdControlTypeFunction : public UIThreadExtensionFunction { public: DECLARE_EXTENSION_FUNCTION("braveShields.setAdControlType", UNKNOWN) diff --git a/chromium_src/components/content_settings/core/browser/brave_content_settings_registry_browsertest2.cc b/chromium_src/components/content_settings/core/browser/brave_content_settings_registry_browsertest2.cc new file mode 100644 index 000000000000..d78691b725a4 --- /dev/null +++ b/chromium_src/components/content_settings/core/browser/brave_content_settings_registry_browsertest2.cc @@ -0,0 +1,46 @@ +/* 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 "base/path_service.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/thread_test_helper.h" +#include "brave/browser/brave_browser_process_impl.h" +#include "brave/browser/extensions/brave_extension_functional_test.h" +#include "brave/common/brave_paths.h" +#include "brave/common/pref_names.h" +#include "brave/common/url_constants.h" +#include "brave/components/brave_shields/browser/https_everywhere_service.h" +#include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/net/url_request_mock_util.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/test/browser_test_utils.h" + +class BraveExtensionBrowserTest : public InProcessBrowserTest { + public: + using InProcessBrowserTest::InProcessBrowserTest; + + void SetUp() override { + InitEmbeddedTestServer(); + InProcessBrowserTest::SetUp(); + } + + void InitEmbeddedTestServer() { + brave::RegisterPathProvider(); + base::FilePath test_data_dir; + base::PathService::Get(brave::DIR_TEST_DATA, &test_data_dir); + embedded_test_server()->ServeFilesFromDirectory(test_data_dir); + ASSERT_TRUE(embedded_test_server()->Start()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(BraveExtensionBrowserTest); +}; + +IN_PROC_BROWSER_TEST_F(BraveExtensionBrowserTest, MutationObserverTriggeredWhenDOMChanged) { + ASSERT_TRUE(true); + GURL url = embedded_test_server()->GetURL("reddit.com", "/cosmetic-filter/mutation_observer.html"); + ui_test_utils::NavigateToURL(browser(), url); +} diff --git a/common/extensions/api/brave_shields.json b/common/extensions/api/brave_shields.json index e52b041cfd40..e2d02a865877 100644 --- a/common/extensions/api/brave_shields.json +++ b/common/extensions/api/brave_shields.json @@ -49,6 +49,68 @@ } ] }, + { + "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": "stylesheet", + "type": "string" + }, + { + "name": "genericExceptions", + "type": "array", + "items": {"type": "string"} + }, + { + "name": "injectedScript", + "type": "string" + } + ] + } + ] + }, + { + "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", @@ -70,6 +132,48 @@ } ] }, + { + "name": "setCosmeticFilteredElements", + "type": "function", + "description": "Set cosmetic filtering type for a url", + "parameters": [ + { + "name": "controlType", + "type": "string" + }, + { + "name": "url", + "type": "string" + }, + { + "type": "function", + "name": "callback", + "optional": true, + "parameters": [] + } + ] + }, + { + "name": "getCosmeticFilteredElements", + "type": "function", + "description": "Get cosmetic filtering type for a url", + "parameters": [ + { + "name": "url", + "type": "string" + }, + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "controlType", + "type": "string" + } + ] + } + ] + }, { "name": "setAdControlType", "type": "function", diff --git a/components/brave_extension/adbox.html b/components/brave_extension/adbox.html new file mode 100644 index 000000000000..4ce5fb9418ca --- /dev/null +++ b/components/brave_extension/adbox.html @@ -0,0 +1,27 @@ + + + +Cosmetic filtering: Test your blocker + + + +

Cosmetic filtering: Test your blocker

+

Force a refresh of the page to test your blocker.

+

Testing EasyList's ...

+

##.ADBox

+
    +
  • <li>: Should not be hidden +
  • <li class="ADBox">: Should be hidden. +
  • <li class="ADBox" style="display: block !important;">: Should be hidden. +
+ + + \ No newline at end of file diff --git a/components/brave_extension/brave_extension_browsertest.cc b/components/brave_extension/brave_extension_browsertest.cc new file mode 100644 index 000000000000..cae2ef449605 --- /dev/null +++ b/components/brave_extension/brave_extension_browsertest.cc @@ -0,0 +1,36 @@ +/* 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 + +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" +#include "base/memory/weak_ptr.h" +#include "brave/browser/ui/views/brave_actions/brave_actions_container.h" +#include "brave/browser/ui/views/location_bar/brave_location_bar_view.h" +#include "brave/common/brave_paths.h" +#include "brave/common/extensions/extension_constants.h" +#include "brave/components/brave_rewards/browser/rewards_service_factory.h" +#include "brave/components/brave_rewards/browser/rewards_service_impl.h" +#include "brave/components/brave_rewards/browser/rewards_service_observer.h" +#include "brave/components/brave_rewards/browser/rewards_notification_service_impl.h" // NOLINT +#include "brave/components/brave_rewards/browser/rewards_notification_service_observer.h" // NOLINT +#include "brave/components/brave_rewards/common/pref_names.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/network_session_configurator/common/network_switches.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/test/browser_test_utils.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/http_request.h" +#include "net/test/embedded_test_server/http_response.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" diff --git a/components/brave_extension/extension/brave_extension/_locales/en_US/messages.json b/components/brave_extension/extension/brave_extension/_locales/en_US/messages.json index 15cedb5616ab..18a001a9f941 100644 --- a/components/brave_extension/extension/brave_extension/_locales/en_US/messages.json +++ b/components/brave_extension/extension/brave_extension/_locales/en_US/messages.json @@ -47,6 +47,10 @@ "message": "connection upgraded", "description": "Message for the main blocked resources text when there is at least one ad/track/script blocked and one connection upgrade (singular)" }, + "firstPartyTrackersBlocked": { + "message": "This site's ads blocked", + "description": "Message for the cosmetic filtering row label" + }, "thirdPartyTrackersBlocked": { "message": "Cross-site trackers blocked", "description": "Message for the scripts blocked row label" diff --git a/components/brave_extension/extension/brave_extension/actions/shieldsPanelActions.ts b/components/brave_extension/extension/brave_extension/actions/shieldsPanelActions.ts index 13c458df61f9..5a1f2c6da170 100644 --- a/components/brave_extension/extension/brave_extension/actions/shieldsPanelActions.ts +++ b/components/brave_extension/extension/brave_extension/actions/shieldsPanelActions.ts @@ -26,6 +26,13 @@ export const resourceBlocked: actions.ResourceBlocked = (details) => { } } +export const hideCosmeticElements: actions.HideCosmeticElements = (setting) => { + return { + type: types.HIDE_COSMETIC_ELEMENTS, + setting + } +} + export const blockAdsTrackers: actions.BlockAdsTrackers = (setting) => { return { type: types.BLOCK_ADS_TRACKERS, 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..ef37bb9098b6 100644 --- a/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts +++ b/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts @@ -6,9 +6,17 @@ export const addSiteCosmeticFilter = async (origin: string, cssfilter: string) = chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { let storeList = Object.assign({}, storeData.cosmeticFilterList) if (storeList[origin] === undefined || storeList[origin].length === 0) { // nothing in filter list for origin - storeList[origin] = [cssfilter] + storeList[origin] = [{ + 'filter': cssfilter, + 'isIdempotent': isIdempotent(cssfilter), + 'applied': false + }] } else { // add entry - storeList[origin].push(cssfilter) + storeList[origin].push({ + 'filter': cssfilter, + 'isIdempotent': isIdempotent(cssfilter), + 'applied': false + }) } chrome.storage.local.set({ 'cosmeticFilterList': storeList }) }) @@ -22,7 +30,27 @@ export const removeSiteFilter = (origin: string) => { }) } -export const applySiteFilters = (tabId: number, hostname: string) => { +export const applyCSSCosmeticFilters = (tabId: number, hostname: string, applyBuiltinFilters: boolean) => { + if (applyBuiltinFilters) { + chrome.braveShields.hostnameCosmeticResources(hostname, (stylesheet, genericExceptions, injectedScript) => { + chrome.tabs.insertCSS(tabId, { + code: stylesheet, + cssOrigin: 'user', + runAt: 'document_start' + }) + + chrome.tabs.sendMessage(tabId, { + type: 'cosmeticFilterGenericExceptions', + exceptions: genericExceptions + }) + + chrome.tabs.executeScript(tabId, { + code: injectedScript, + runAt: 'document_start' + }); + }) + } + chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { if (!storeData.cosmeticFilterList) { if (process.env.NODE_ENV === 'shields_development') { @@ -31,12 +59,12 @@ export const applySiteFilters = (tabId: number, hostname: string) => { return } if (storeData.cosmeticFilterList[hostname] !== undefined) { - storeData.cosmeticFilterList[hostname].map((rule: string) => { + storeData.cosmeticFilterList[hostname].map((rule: any) => { // if the filter hasn't been applied once before, apply it and set the corresponding filter to true if (process.env.NODE_ENV === 'shields_development') { - console.log('applying rule', rule) + console.log('applying filter', rule.filter) } chrome.tabs.insertCSS(tabId, { // https://github.com/brave/brave-browser/wiki/Cosmetic-Filtering - code: `${rule} {display: none !important;}`, + code: `${rule.filter} {display:none!important;}`, cssOrigin: 'user', runAt: 'document_start' }) @@ -48,3 +76,37 @@ export const applySiteFilters = (tabId: number, hostname: string) => { export const removeAllFilters = () => { chrome.storage.local.set({ 'cosmeticFilterList': {} }) } + +function isIdempotent (str: String) { + // if string contains a non-idempotent string, the selector is not idempotent + // https://www.w3schools.com/cssref/css_selectors.asp + + let nonIdempotentStrings = [ + ':active', + '::after', + '::before', + ':checked', + ':default', + ':disabled', + ':empty', + ':enabled', + ':first-child', + '::first-letter', + '::first-line', + ':first-of-type', + ':focus', + ':hover', + ':last-child', + ':last-of-type', + ':nth-child', + ':nth-last-child', + ':nth-last-of-type', + ':nth-of-type'] + + for (let i = 0; i < nonIdempotentStrings.length; i++) { + if (str.includes(nonIdempotentStrings[i])) { + return true + } + } + return false +} 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 2167dd1a0325..bb7a594664db 100644 --- a/components/brave_extension/extension/brave_extension/background/api/shieldsAPI.ts +++ b/components/brave_extension/extension/brave_extension/background/api/shieldsAPI.ts @@ -23,6 +23,7 @@ export const getShieldSettingsForTabData = (tabData?: chrome.tabs.Tab) => { return Promise.all([ chrome.braveShields.getBraveShieldsEnabledAsync(tabData.url), + chrome.braveShields.getCosmeticFilteredElementsAsync(tabData.url), chrome.braveShields.getAdControlTypeAsync(tabData.url), chrome.braveShields.getHTTPSEverywhereEnabledAsync(tabData.url), chrome.braveShields.getNoScriptControlTypeAsync(tabData.url), @@ -35,12 +36,13 @@ export const getShieldSettingsForTabData = (tabData?: chrome.tabs.Tab) => { hostname, id: tabData.id, braveShields: details[0] ? 'allow' : 'block', - ads: details[1], - trackers: details[1], - httpUpgradableResources: details[2] ? 'block' : 'allow', - javascript: details[3], - fingerprinting: details[4], - cookies: details[5] + cosmeticBlocking: details[1], + ads: details[2], + trackers: details[2], + httpUpgradableResources: details[3] ? 'block' : 'allow', + javascript: details[4], + fingerprinting: details[5], + cookies: details[6] } }).catch(() => { return { @@ -87,6 +89,16 @@ export const requestShieldPanelData = (tabId: number) => export const setAllowBraveShields = (origin: string, setting: string) => chrome.braveShields.setBraveShieldsEnabledAsync(setting === 'allow' ? true : false, origin) +/** + * Changes elements affected by cosmetic filtering at origin to be allowed or blocked. + * The cosmetic ad-block service will come into effect if the ad is marked as blocked. + * @param {string} origin the origin of the site to change the setting for + * @param {string} setting 'allow' or 'block' + * @return a promise which resolves when the setting is set + */ +export const setAllowCosmeticElements = (origin: string, setting: string) => + chrome.braveShields.setCosmeticFilteredElementsAsync(setting, origin) + /** * Changes the ads at origin to be allowed or blocked. * The ad-block service will come into effect if the ad is marked as blocked. 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 817c6b25ea2c..eb06f15a77e3 100644 --- a/components/brave_extension/extension/brave_extension/background/events/cosmeticFilterEvents.ts +++ b/components/brave_extension/extension/brave_extension/background/events/cosmeticFilterEvents.ts @@ -45,6 +45,16 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { rule.host = msg.baseURI break } + case 'classIdStylesheet': { + chrome.braveShields.classIdStylesheet(msg.classes, msg.ids, msg.exceptions, stylesheet => { + chrome.tabs.insertCSS({ + code: stylesheet, + cssOrigin: 'user', + runAt: 'document_start', + }) + }) + break + } } }) diff --git a/components/brave_extension/extension/brave_extension/background/reducers/cosmeticFilterReducer.ts b/components/brave_extension/extension/brave_extension/background/reducers/cosmeticFilterReducer.ts index f42ae60cd981..6025c57b352c 100644 --- a/components/brave_extension/extension/brave_extension/background/reducers/cosmeticFilterReducer.ts +++ b/components/brave_extension/extension/brave_extension/background/reducers/cosmeticFilterReducer.ts @@ -20,7 +20,8 @@ import { reloadTab } from '../api/tabsAPI' import { removeSiteFilter, addSiteCosmeticFilter, - applySiteFilters, + // applyDOMCosmeticFilters, + applyCSSCosmeticFilters, removeAllFilters } from '../api/cosmeticFilterAPI' @@ -69,8 +70,8 @@ export default function cosmeticFilterReducer ( state = shieldsPanelState.resetBlockingStats(state, action.tabId) state = shieldsPanelState.resetBlockingResources(state, action.tabId) state = noScriptState.resetNoScriptInfo(state, action.tabId, getOrigin(action.url)) + applyCSSCosmeticFilters(action.tabId, getHostname(action.url), tabData.cosmeticBlocking === "block") } - applySiteFilters(action.tabId, getHostname(action.url)) break } case windowTypes.WINDOW_REMOVED: { @@ -146,10 +147,20 @@ export default function cosmeticFilterReducer ( break } case cosmeticFilterTypes.SITE_COSMETIC_FILTER_ADDED: { - addSiteCosmeticFilter(action.origin, action.cssfilter) - .catch((e) => { - console.error('Could not add filter:', e) - }) + const tabData = shieldsPanelState.getActiveTabData(state) + const tabId: number = shieldsPanelState.getActiveTabId(state) + if (!tabData) { + console.error('Active tab not found') + break + } + addSiteCosmeticFilter(tabData.hostname, action.cssfilter) + .then(() => { + console.log(`added: ${tabData.hostname} | ${action.cssfilter}`) + }) + .catch(e => { + console.error('Could not add filter:', e) + }) + applyCSSCosmeticFilters(tabId, tabData.hostname, tabData.cosmeticBlocking === "block") break } } 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 9ffa54bb58b0..975bdf4d4f2c 100644 --- a/components/brave_extension/extension/brave_extension/background/reducers/shieldsPanelReducer.ts +++ b/components/brave_extension/extension/brave_extension/background/reducers/shieldsPanelReducer.ts @@ -20,6 +20,7 @@ import * as noScriptState from '../../state/noScriptState' // APIs import { setAllowBraveShields, + setAllowCosmeticElements, setAllowAds, setAllowTrackers, setAllowHTTPUpgradableResources, @@ -173,6 +174,27 @@ export default function shieldsPanelReducer ( } break } + case shieldsPanelTypes.HIDE_COSMETIC_ELEMENTS: { + const tabId: number = shieldsPanelState.getActiveTabId(state) + const tabData = shieldsPanelState.getActiveTabData(state) + + if (!tabData) { + console.error('Active tab not found') + break + } + + setAllowCosmeticElements(tabData.origin, toggleShieldsValue(action.setting)) + .then(() => { + requestShieldPanelData(shieldsPanelState.getActiveTabId(state)) + reloadTab(tabId, true).catch(() => { + console.error('Tab reload was not successful') + }) + }) + .catch(() => { + console.error('Could not set cosmetic blocking setting') + }) + break + } case shieldsPanelTypes.BLOCK_ADS_TRACKERS: { const tabId: number = shieldsPanelState.getActiveTabId(state) const tabData = shieldsPanelState.getActiveTabData(state) diff --git a/components/brave_extension/extension/brave_extension/components/advancedView/controls/cosmeticBlockingControl.tsx b/components/brave_extension/extension/brave_extension/components/advancedView/controls/cosmeticBlockingControl.tsx new file mode 100644 index 000000000000..e993b9c08586 --- /dev/null +++ b/components/brave_extension/extension/brave_extension/components/advancedView/controls/cosmeticBlockingControl.tsx @@ -0,0 +1,75 @@ +/* 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/. */ + +import * as React from 'react' + +// Feature-specific components +import { + BlockedInfoRow, + BlockedInfoRowSingleText, + //BlockedInfoRowData, + Toggle +} from 'brave-ui/features/shields' + +// Locale +import { getLocale } from '../../../background/api/localeAPI' + +// Helpers +import { + maybeBlockResource, + getToggleStateViaEventTarget, +} from '../../../helpers/shieldsUtils' + +// Types +import { HideCosmeticElements } from '../../../types/actions/shieldsPanelActions' +import { BlockOptions } from '../../../types/other/blockTypes' + +interface CommonProps { + isBlockedListOpen: boolean + setBlockedListOpen: () => void +} + +interface CosmeticBlockingProps { + cosmeticBlocking: BlockOptions + hideCosmeticElements: HideCosmeticElements +} + +export type Props = CommonProps & CosmeticBlockingProps + +interface State {} + +export default class CosmeticBlockingControl extends React.PureComponent { + constructor (props: Props) { + super(props) + this.state = {} + } + + get maybeBlock1stPartyTrackersBlocked (): boolean { + const { cosmeticBlocking } = this.props + return maybeBlockResource(cosmeticBlocking) + } + + onChange1stPartyTrackersBlockedEnabled = (event: React.ChangeEvent) => { + const shouldEnableCosmeticBlocking = getToggleStateViaEventTarget(event) + this.props.hideCosmeticElements(shouldEnableCosmeticBlocking) + } + + render () { + const { isBlockedListOpen } = this.props + return ( + <> + + {getLocale('firstPartyTrackersBlocked')} + + + + ) + } +} diff --git a/components/brave_extension/extension/brave_extension/components/advancedView/index.tsx b/components/brave_extension/extension/brave_extension/components/advancedView/index.tsx index 26ceeb356d6c..4f14b572b84f 100644 --- a/components/brave_extension/extension/brave_extension/components/advancedView/index.tsx +++ b/components/brave_extension/extension/brave_extension/components/advancedView/index.tsx @@ -22,6 +22,7 @@ import { Tab, PersistentData } from '../../types/state/shieldsPannelState' import { isShieldsEnabled, getFavicon } from '../../helpers/shieldsUtils' import { ShieldsToggled, + HideCosmeticElements, BlockAdsTrackers, HttpsEverywhereToggled, BlockJavaScript, @@ -38,6 +39,7 @@ import { interface Props { actions: { shieldsToggled: ShieldsToggled + hideCosmeticElements: HideCosmeticElements blockAdsTrackers: BlockAdsTrackers httpsEverywhereToggled: HttpsEverywhereToggled blockJavaScript: BlockJavaScript @@ -114,6 +116,9 @@ export default class Shields extends React.PureComponent { setBlockedListOpen={this.setBlockedListOpen} hostname={shieldsPanelTabData.hostname} favicon={this.favicon} + // Cosmetic Blocking + cosmeticBlocking={shieldsPanelTabData.cosmeticBlocking} + hideCosmeticElements={actions.hideCosmeticElements} // Ads/Trackers ads={shieldsPanelTabData.ads} adsBlocked={shieldsPanelTabData.adsBlocked} diff --git a/components/brave_extension/extension/brave_extension/components/advancedView/interfaceControls.tsx b/components/brave_extension/extension/brave_extension/components/advancedView/interfaceControls.tsx index 52ed152b938f..cf611c3c9ef4 100644 --- a/components/brave_extension/extension/brave_extension/components/advancedView/interfaceControls.tsx +++ b/components/brave_extension/extension/brave_extension/components/advancedView/interfaceControls.tsx @@ -5,11 +5,12 @@ import * as React from 'react' // Group Components +import CosmeticBlockingControl from './controls/cosmeticBlockingControl' import AdsTrackersControl from './controls/adsTrackersControl' import HTTPSUpgradesControl from './controls/httpsUpgradesControl' // Types -import { BlockAdsTrackers, HttpsEverywhereToggled } from '../../types/actions/shieldsPanelActions' +import { HideCosmeticElements, BlockAdsTrackers, HttpsEverywhereToggled } from '../../types/actions/shieldsPanelActions' import { BlockOptions } from '../../types/other/blockTypes' interface CommonProps { @@ -19,6 +20,11 @@ interface CommonProps { favicon: string } +interface CosmeticBlockingProps { + cosmeticBlocking: BlockOptions + hideCosmeticElements: HideCosmeticElements +} + interface AdsTrackersProps { ads: BlockOptions adsBlocked: number @@ -36,7 +42,7 @@ interface HTTPSUpgradesProps { httpsEverywhereToggled: HttpsEverywhereToggled } -export type Props = CommonProps & AdsTrackersProps & HTTPSUpgradesProps +export type Props = CommonProps & CosmeticBlockingProps & AdsTrackersProps & HTTPSUpgradesProps export default class InterfaceControls extends React.PureComponent { get commonProps (): CommonProps { @@ -47,6 +53,11 @@ export default class InterfaceControls extends React.PureComponent { render () { return ( <> + | undefined = undefined +let classIdBuffer: { classes: string[], ids: string[] } = { classes: [], ids: [] } + +if (process.env.NODE_ENV === 'development') { + console.info('development content script here') +} function getCurrentURL () { return window.location.hostname } +// when page loads, grab filter list and only activate if there are rules +chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { + if (!storeData.cosmeticFilterList) { + if (process.env.NODE_ENV === 'development') { + console.info('applySiteFilters: no cosmetic filter store yet') + } + return + } + console.log('storeData.cosmeticFilterList', storeData.cosmeticFilterList) + console.log('storeData.cosmeticFilterList[getCurrentURL()]', storeData.cosmeticFilterList[getCurrentURL()]) + // add length check here (can't read property slice of undefined) + if (storeData.cosmeticFilterList[getCurrentURL()]) { + contentSiteFilters = storeData.cosmeticFilterList[getCurrentURL()].slice() + console.log('contentSiteFilters', contentSiteFilters) + console.log('current site list in content script', contentSiteFilters) + } +}) + +// let debouncedRemove = debounce((siteFilters: Array) => { +// console.log('REMOVING HERE') +// removeAll(siteFilters) +// }, 1000 / 60) + +// on load retrieve each website's filter list +chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { // fetch filter list + // !storeData.cosmeticFilterList || storeData.cosmeticFilterList.length === 0 // if no rules, don't apply mutation observer + + if (!storeData.cosmeticFilterList) { + console.log('storeData.cosmeticFilterList does not exist') + } else if (Object.keys(storeData.cosmeticFilterList).length === 0) { + console.log('storeData.cosmeticFilterList length === 0') + } else { + console.log('ON COMMITTED MUTATION OBSERVER BEING APPLIED:') + // removeAll(contentSiteFilters) + chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { // fetch filter list + console.log('cosmeticFilterList.length:', Object.keys(storeData.cosmeticFilterList).length) + }) + } +}) + +function applyDOMCosmeticFilterDebounce() { + console.log('applyDOMCosmeticFilterDebounce call') + 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 + // characterData: true + } + observer.observe(targetNode, observerConfig) +} + +/*const injectIncrementalStyles = (mutations: MutationRecord[]) => { + // array of site filters, go through each one and check if idempotent/already applied + + siteFilters.map((mutations: any) => { + console.log(filterData.filter) + if (!filterData.isIdempotent || !filterData.applied) { // don't apply if filter is idempotent AND was already applied + if (document.querySelector(filterData.filter)) { // attempt filter application + document.querySelectorAll(filterData.filter).forEach(e => { + console.log(filterData.filter, document.querySelectorAll(filterData.filter)) + e.remove() + filterData.applied = true + }) + } + console.log(siteFilters) + } + }) +}*/ +/* + let contentSiteFilters = [ + { + 'filter': 'filter1', + 'isIdempotent': true, + 'applied': false + }, { + 'filter': 'filter2', + 'isIdempotent': false, + 'applied': false + }, { + 'filter': 'filter3', + 'isIdempotent': false, + 'applied': false + } + ] +*/ + +const handleNewNodes = (newNodes: Element[]) => { + const { classes, ids } = getClassesAndIds(newNodes); + if (!(genericExceptions)) { + classIdBuffer.classes.push(...classes) + classIdBuffer.ids.push(...ids) + } else { + chrome.runtime.sendMessage({ + type: 'classIdStylesheet', + classes, + ids, + exceptions: genericExceptions, + }) + } +} + +const getClassesAndIds = (function() { + const queriedIds = new Set(); + const queriedClasses = new Set(); + const regexWhitespace = /\s/; + + const new_classes_and_ids = function(addedNodes: Element[]) { + const ids = []; + const classes = []; + + for (const node of addedNodes) { + let v = node.id; + if (typeof v === 'string' && v.length !== 0) { + v = v.trim(); + if(!queriedIds.has(v) && v.length !== 0) { + ids.push(v); + queriedIds.add(v); + } + } + let vv = node.className; + if (typeof vv === 'string' && vv.length !== 0 && !regexWhitespace.test(vv)) { + if(!queriedClasses.has(vv)) { + classes.push(vv); + queriedClasses.add(vv); + } + } else { + let vvv = node.classList; + if (vvv !== undefined) { + let j = vvv.length; + while (j--) { + const v = vvv[j]; + if(queriedClasses.has(v) === false) { + classes.push(v); + queriedClasses.add(v); + } + } + } + } + } + return {classes, ids}; + }; + + return new_classes_and_ids; +})(); + +let allNodes = Array.from(document.querySelectorAll('[id],[class]')) +handleNewNodes(allNodes) +applyDOMCosmeticFilterDebounce() + document.addEventListener('contextmenu', (event) => { // send host and store target // `target` needed for when background page handles `addBlockElement` @@ -21,5 +188,17 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { case 'getTargetSelector': { sendResponse(unique(target)) } + case 'cosmeticFilterGenericExceptions': { + genericExceptions = msg.exceptions + if (classIdBuffer.classes.length !== 0 || classIdBuffer.ids.length !== 0) { + chrome.runtime.sendMessage({ + type: 'classIdStylesheet', + classes: classIdBuffer.classes, + ids: classIdBuffer.ids, + exceptions: genericExceptions, + }) + } + sendResponse(null) + } } }) 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/types/actions/shieldsPanelActions.ts b/components/brave_extension/extension/brave_extension/types/actions/shieldsPanelActions.ts index ed68662c1719..4c9fd941577b 100644 --- a/components/brave_extension/extension/brave_extension/types/actions/shieldsPanelActions.ts +++ b/components/brave_extension/extension/brave_extension/types/actions/shieldsPanelActions.ts @@ -50,6 +50,15 @@ export interface ResourceBlocked { (details: BlockDetails): ResourceBlockedReturn } +interface HideCosmeticElementsReturn { + type: types.HIDE_COSMETIC_ELEMENTS + setting: BlockOptions +} + +export interface HideCosmeticElements { + (setting: BlockOptions): HideCosmeticElementsReturn +} + interface BlockAdsTrackersReturn { type: types.BLOCK_ADS_TRACKERS setting: BlockOptions @@ -160,6 +169,7 @@ export type shieldPanelActions = ShieldsPanelDataUpdatedReturn | ShieldsToggledReturn | ResourceBlockedReturn | + HideCosmeticElementsReturn | BlockAdsTrackersReturn | ControlsToggledReturn | HttpsEverywhereToggledReturn | 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 4835aea808b8..f4dfaebfd5fc 100644 --- a/components/brave_extension/extension/brave_extension/types/constants/shieldsPanelTypes.ts +++ b/components/brave_extension/extension/brave_extension/types/constants/shieldsPanelTypes.ts @@ -7,6 +7,7 @@ import * as types from '../../constants/shieldsPanelTypes' export type SHIELDS_PANEL_DATA_UPDATED = typeof types.SHIELDS_PANEL_DATA_UPDATED export type SHIELDS_TOGGLED = typeof types.SHIELDS_TOGGLED export type RESOURCE_BLOCKED = typeof types.RESOURCE_BLOCKED +export type HIDE_COSMETIC_ELEMENTS = typeof types.HIDE_COSMETIC_ELEMENTS export type BLOCK_ADS_TRACKERS = typeof types.BLOCK_ADS_TRACKERS export type CONTROLS_TOGGLED = typeof types.CONTROLS_TOGGLED export type HTTPS_EVERYWHERE_TOGGLED = typeof types.HTTPS_EVERYWHERE_TOGGLED 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..dcca8fd693b0 100644 --- a/components/brave_extension/extension/brave_extension/types/state/shieldsPannelState.ts +++ b/components/brave_extension/extension/brave_extension/types/state/shieldsPannelState.ts @@ -6,6 +6,7 @@ import { BlockOptions, BlockTypes, BlockFPOptions, BlockCookiesOptions } from '. import { NoScriptInfo } from '../other/noScriptInfo' export interface Tab { + cosmeticBlocking: BlockOptions ads: BlockOptions adsBlocked: number controlsOpen: boolean diff --git a/components/brave_extension/extension/brave_extension_browsertest.cc b/components/brave_extension/extension/brave_extension_browsertest.cc new file mode 100644 index 000000000000..37a83ca04f8e --- /dev/null +++ b/components/brave_extension/extension/brave_extension_browsertest.cc @@ -0,0 +1,71 @@ +// /* 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 "brave/components/brave_shields/common/brave_shield_constants.h" +// #include "chrome/browser/content_settings/host_content_settings_map_factory.h" +// #include "chrome/browser/profiles/profile.h" +// #include "chrome/browser/ui/browser.h" +// #include "chrome/test/base/in_process_browser_test.h" +// #include "components/content_settings/core/browser/host_content_settings_map.h" +// #include "components/content_settings/core/common/content_settings_pattern.h" + +// // npm run test -- brave_browser_tests --filter=BraveExtensionBrowserTest.* +// class BraveExtensionBrowserTest : public InProcessBrowserTest { +// private: +// DISALLOW_COPY_AND_ASSIGN(BraveExtensionBrowserTest); + +// public: +// using InProcessBrowserTest::InProcessBrowserTest; + +// BraveExtensionBrowserTest() {} + +// void SetUp() override { +// InitEmbeddedTestServer(); +// InitService(); +// ExtensionBrowserTest::SetUp(); +// } +// void SetUpOnMainThread() override { +// ExtensionBrowserTest::SetUpOnMainThread(); +// // base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::IO}, +// // base::BindOnce(&chrome_browser_net::SetUrlRequestMocksEnabled, true)); +// // host_resolver()->AddRule("*", "127.0.0.1"); +// } +// void PreRunTestOnMainThread() override { +// ExtensionBrowserTest::PreRunTestOnMainThread(); +// // WaitForHTTPSEverywhereServiceThread(); +// // ASSERT_TRUE( +// // g_brave_browser_process->https_everywhere_service()->IsInitialized()); +// } +// void InitEmbeddedTestServer() { +// brave::RegisterPathProvider(); +// base::FilePath test_data_dir; +// base::PathService::Get(brave::DIR_TEST_DATA, &test_data_dir); +// embedded_test_server()->ServeFilesFromDirectory(test_data_dir); +// ASSERT_TRUE(embedded_test_server()->Start()); +// } +// // void InitService() { +// // brave_shields::kBraveExtensionTestId::SetIgnorePortForTest(true); +// // brave_shields::kBraveExtensionTestId:: +// // SetComponentIdAndBase64PublicKeyForTest( +// // kBraveExtensionTestId, +// // kBraveExtensionTestBase64PublicKey); +// // } + +// }; + +// IN_PROC_BROWSER_TEST_F(BraveExtensionBrowserTest, MutationObserverTriggeredWhenDOMChanged) { +// ASSERT_TRUE(true); +// GURL url = embedded_test_server()->GetURL("a.com", "/mutation_observer.html"); +// ui_test_utils::NavigateToURL(browser(), url); + +// GURL iframe_url = embedded_test_server()->GetURL("www.digg.com", "/"); +// const char kIframeID[] = "test"; +// content::WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); +// EXPECT_TRUE(NavigateIframeToURL(contents, kIframeID, iframe_url)); +// content::RenderFrameHost* iframe_contents = ChildFrameAt(contents->GetMainFrame(), 0); +// WaitForLoadStop(contents); +// EXPECT_EQ(GURL("https://www.digg.com/"), iframe_contents->GetLastCommittedURL()); + +// } diff --git a/components/brave_extension/index.html b/components/brave_extension/index.html new file mode 100644 index 000000000000..1cca69b67b8a --- /dev/null +++ b/components/brave_extension/index.html @@ -0,0 +1,11 @@ + + + + + + +

Mutation Observer Test Page

+ + + + \ No newline at end of file diff --git a/components/brave_extension/index.js b/components/brave_extension/index.js new file mode 100644 index 000000000000..c2ecbccbd238 --- /dev/null +++ b/components/brave_extension/index.js @@ -0,0 +1,18 @@ +let n = 0 +function append() { + var node = document.createElement('div') + var textnode = document.createTextNode(n) + node.appendChild(textnode) + document.getElementsByTagName('body')[0].appendChild(node) + n++ +} +function append10() { + let fragment = document.createDocumentFragment() + for (let i = 0; i< 10; i++) { + let p = document.createElement('div') + p.textContent = n + fragment.appendChild(p) + n++ + } + document.getElementsByTagName('body')[0].appendChild(fragment) +} diff --git a/components/brave_extension/mutationObserver.html b/components/brave_extension/mutationObserver.html new file mode 100644 index 000000000000..f39a269cd1d2 --- /dev/null +++ b/components/brave_extension/mutationObserver.html @@ -0,0 +1,12 @@ + + + + + + + +

Mutation Observer Test Page

+ + + + \ No newline at end of file diff --git a/components/brave_shields/browser/ad_block_base_service.cc b/components/brave_shields/browser/ad_block_base_service.cc index 1461f03822e3..ce56ec33b0ce 100644 --- a/components/brave_shields/browser/ad_block_base_service.cc +++ b/components/brave_shields/browser/ad_block_base_service.cc @@ -20,7 +20,6 @@ #include "brave/common/pref_names.h" #include "brave/components/brave_component_updater/browser/dat_file_util.h" #include "brave/components/brave_shields/common/brave_shield_constants.h" -#include "brave/vendor/adblock_rust_ffi/src/wrapper.hpp" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" @@ -188,6 +187,14 @@ bool AdBlockBaseService::TagExists(const std::string& tag) { return std::find(tags_.begin(), tags_.end(), tag) != tags_.end(); } +adblock::HostnameResources AdBlockBaseService::hostnameCosmeticResources(const std::string& hostname) { + return 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::PostTaskWithTraitsAndReplyWithResult( FROM_HERE, {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 a97179aabbb8..cbd0bdbe53ce 100644 --- a/components/brave_shields/browser/ad_block_base_service.h +++ b/components/brave_shields/browser/ad_block_base_service.h @@ -19,6 +19,7 @@ #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" +#include "brave/vendor/adblock_rust_ffi/src/wrapper.hpp" class AdBlockServiceTest; @@ -45,6 +46,9 @@ class AdBlockBaseService : public BaseBraveShieldsService { void EnableTag(const std::string& tag, bool enabled); bool TagExists(const std::string& tag); + adblock::HostnameResources 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/brave_shields_util.cc b/components/brave_shields/browser/brave_shields_util.cc index 5af0943aed69..a03a9ad43ed2 100644 --- a/components/brave_shields/browser/brave_shields_util.cc +++ b/components/brave_shields/browser/brave_shields_util.cc @@ -150,6 +150,30 @@ bool GetBraveShieldsEnabled(Profile* profile, const GURL& url) { HostContentSettingsMapFactory::GetForProfile(profile), url); } +void SetCosmeticFilteredElements(Profile* profile, ControlType type, const GURL& url) { + DCHECK(type != ControlType::BLOCK_THIRD_PARTY); + auto primary_pattern = GetPatternFromURL(url); + + if (!primary_pattern.IsValid()) { + return; + } + + HostContentSettingsMapFactory::GetForProfile(profile) + ->SetContentSettingCustomScope(primary_pattern, + ContentSettingsPattern::Wildcard(), + CONTENT_SETTINGS_TYPE_PLUGINS, kCosmeticFilteredElements, + GetDefaultBlockFromControlType(type)); +} + +ControlType GetCosmeticFilteredElements(Profile* profile, const GURL& url) { + ContentSetting setting = + HostContentSettingsMapFactory::GetForProfile(profile)->GetContentSetting( + url, GURL(), CONTENT_SETTINGS_TYPE_PLUGINS, kCosmeticFilteredElements); + + return setting == CONTENT_SETTING_ALLOW ? ControlType::ALLOW + : ControlType::BLOCK; +} + void SetAdControlType(Profile* profile, ControlType type, const GURL& url) { DCHECK(type != ControlType::BLOCK_THIRD_PARTY); auto primary_pattern = GetPatternFromURL(url); diff --git a/components/brave_shields/browser/brave_shields_util.h b/components/brave_shields/browser/brave_shields_util.h index fa4f1e1ac1a6..bae8612ce4a9 100644 --- a/components/brave_shields/browser/brave_shields_util.h +++ b/components/brave_shields/browser/brave_shields_util.h @@ -42,6 +42,9 @@ void ResetBraveShieldsEnabled(Profile* profile, const GURL& url); bool GetBraveShieldsEnabled(Profile* profile, const GURL& url); bool GetBraveShieldsEnabled(HostContentSettingsMap* map, const GURL& url); +void SetCosmeticFilteredElements(Profile* profile, ControlType type, const GURL& url); +ControlType GetCosmeticFilteredElements(Profile* profile, const GURL& url); + void SetAdControlType(Profile* profile, ControlType type, const GURL& url); ControlType GetAdControlType(Profile* profile, const GURL& url); diff --git a/components/brave_shields/common/brave_shield_constants.h b/components/brave_shields/common/brave_shield_constants.h index 8ab830cc80c2..b5f66b1420ff 100644 --- a/components/brave_shields/common/brave_shield_constants.h +++ b/components/brave_shields/common/brave_shield_constants.h @@ -9,6 +9,7 @@ namespace brave_shields { +const char kCosmeticFilteredElements[] = "cosmeticFilteredElements"; const char kAds[] = "ads"; const char kTrackers[] = "trackers"; const char kHTTPUpgradableResources[] = "httpUpgradableResources"; diff --git a/components/definitions/chromel.d.ts b/components/definitions/chromel.d.ts index 50489d04b307..b053e24ac420 100644 --- a/components/definitions/chromel.d.ts +++ b/components/definitions/chromel.d.ts @@ -188,6 +188,8 @@ declare namespace chrome.braveShields { const allowScriptsOnce: any const setBraveShieldsEnabledAsync: any const getBraveShieldsEnabledAsync: any + const setCosmeticFilteredElementsAsync: any + const getCosmeticFilteredElementsAsync: any const setAdControlTypeAsync: any const getAdControlTypeAsync: any const setCookieControlTypeAsync: any @@ -199,6 +201,9 @@ declare namespace chrome.braveShields { const setNoScriptControlTypeAsync: any const getNoScriptControlTypeAsync: any + const hostnameCosmeticResources: (hostname: string, callback: (stylesheet: string, genericExceptions: string[], injectedScript: string) => void) => void + const classIdStylesheet: (classes: string[], ids: string[], exceptions: string[], callback: (stylesheet: string) => void) => void + type BraveShieldsViewPreferences = { showAdvancedView: boolean } diff --git a/test/BUILD.gn b/test/BUILD.gn index 4b8185c210ab..97dee9a8637b 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -379,6 +379,7 @@ test("brave_browser_tests") { "//brave/chromium_src/chrome/browser/ui/autofill/payments/brave_save_card_bubble_controller_impl_browsertest.cc", "//brave/chromium_src/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc", "//brave/chromium_src/components/content_settings/core/browser/brave_content_settings_registry_browsertest.cc", + "//brave/chromium_src/components/content_settings/core/browser/brave_content_settings_registry_browsertest2.cc", "//brave/chromium_src/third_party/blink/renderer/modules/battery/navigator_batterytest.cc", "//brave/chromium_src/third_party/blink/renderer/modules/bluetooth/navigator_bluetoothtest.cc", "//brave/browser/autoplay/autoplay_permission_context_browsertest.cc", @@ -433,6 +434,7 @@ test("brave_browser_tests") { "//chrome/browser/extensions/extension_function_test_utils.h", "//chrome/browser/extensions/updater/extension_cache_fake.cc", "//chrome/browser/extensions/updater/extension_cache_fake.h", + "//brave/components/brave_extension/brave_extension_browsertest.cc", ] deps = [] diff --git a/test/data/cosmetic-filter/mutation_observer.html b/test/data/cosmetic-filter/mutation_observer.html new file mode 100644 index 000000000000..041de9b75721 --- /dev/null +++ b/test/data/cosmetic-filter/mutation_observer.html @@ -0,0 +1,11 @@ + + + + + + +

Mutation Observer Test Page

+ + + + \ No newline at end of file diff --git a/test/data/cosmetic-filter/mutation_observer.js b/test/data/cosmetic-filter/mutation_observer.js new file mode 100644 index 000000000000..c2ecbccbd238 --- /dev/null +++ b/test/data/cosmetic-filter/mutation_observer.js @@ -0,0 +1,18 @@ +let n = 0 +function append() { + var node = document.createElement('div') + var textnode = document.createTextNode(n) + node.appendChild(textnode) + document.getElementsByTagName('body')[0].appendChild(node) + n++ +} +function append10() { + let fragment = document.createDocumentFragment() + for (let i = 0; i< 10; i++) { + let p = document.createElement('div') + p.textContent = n + fragment.appendChild(p) + n++ + } + document.getElementsByTagName('body')[0].appendChild(fragment) +}