From c52cde9cf05b098aa0e81bd7a304f70817c82aae Mon Sep 17 00:00:00 2001 From: Anton Lazarev Date: Thu, 8 Aug 2019 16:22:14 -0700 Subject: [PATCH 1/6] expose cosmetic filtering javascript API --- browser/extensions/api/brave_shields_api.cc | 41 ++++++++++++ browser/extensions/api/brave_shields_api.h | 20 ++++++ common/extensions/api/brave_shields.json | 63 +++++++++++++++++++ .../browser/ad_block_base_service.cc | 9 ++- .../browser/ad_block_base_service.h | 4 ++ components/definitions/chromel.d.ts | 3 + 6 files changed, 139 insertions(+), 1 deletion(-) diff --git a/browser/extensions/api/brave_shields_api.cc b/browser/extensions/api/brave_shields_api.cc index b979bcef50ef..67a6f3aea0d2 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,43 @@ 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)); + + base::Value injections(base::Value::Type::LIST); + for(auto i = resources.script_injections.begin(); i != resources.script_injections.end(); i++) { + injections.GetList().push_back(base::Value(*i)); + } + result_list->GetList().push_back(std::move(injections)); + + 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_)); diff --git a/browser/extensions/api/brave_shields_api.h b/browser/extensions/api/brave_shields_api.h index 59380d2de9eb..4170fd773c75 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) diff --git a/common/extensions/api/brave_shields.json b/common/extensions/api/brave_shields.json index e52b041cfd40..5ea968cc9ec5 100644 --- a/common/extensions/api/brave_shields.json +++ b/common/extensions/api/brave_shields.json @@ -49,6 +49,69 @@ } ] }, + { + "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": "scriptInjections", + "type": "array", + "items": {"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", 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/definitions/chromel.d.ts b/components/definitions/chromel.d.ts index 50489d04b307..4035b10a3479 100644 --- a/components/definitions/chromel.d.ts +++ b/components/definitions/chromel.d.ts @@ -199,6 +199,9 @@ declare namespace chrome.braveShields { const setNoScriptControlTypeAsync: any const getNoScriptControlTypeAsync: any + const hostnameCosmeticResources: (hostname: string, callback: (stylesheet: string, genericExceptions: string[], scriptInjections: string[]) => void) => void + const classIdStylesheet: (classes: string[], ids: string[], exceptions: string[], callback: (stylesheet: string) => void) => void + type BraveShieldsViewPreferences = { showAdvancedView: boolean } From 7a0d6e72591c3bcc1903dfc837773f5edbebd59d Mon Sep 17 00:00:00 2001 From: Anton Lazarev Date: Tue, 20 Aug 2019 13:40:10 -0700 Subject: [PATCH 2/6] return a single script for cosmetic scriptlet injection --- browser/extensions/api/brave_shields_api.cc | 6 +----- common/extensions/api/brave_shields.json | 5 ++--- components/definitions/chromel.d.ts | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/browser/extensions/api/brave_shields_api.cc b/browser/extensions/api/brave_shields_api.cc index 67a6f3aea0d2..26fd3deee063 100644 --- a/browser/extensions/api/brave_shields_api.cc +++ b/browser/extensions/api/brave_shields_api.cc @@ -57,11 +57,7 @@ ExtensionFunction::ResponseAction BraveShieldsHostnameCosmeticResourcesFunction: } result_list->GetList().push_back(std::move(exceptions)); - base::Value injections(base::Value::Type::LIST); - for(auto i = resources.script_injections.begin(); i != resources.script_injections.end(); i++) { - injections.GetList().push_back(base::Value(*i)); - } - result_list->GetList().push_back(std::move(injections)); + result_list->GetList().push_back(base::Value(resources.injected_script)); return RespondNow(ArgumentList(std::move(result_list))); } diff --git a/common/extensions/api/brave_shields.json b/common/extensions/api/brave_shields.json index 5ea968cc9ec5..26f1ba3ce7fe 100644 --- a/common/extensions/api/brave_shields.json +++ b/common/extensions/api/brave_shields.json @@ -72,9 +72,8 @@ "items": {"type": "string"} }, { - "name": "scriptInjections", - "type": "array", - "items": {"type": "string"} + "name": "injectedScript", + "type": "string" } ] } diff --git a/components/definitions/chromel.d.ts b/components/definitions/chromel.d.ts index 4035b10a3479..a3c191737135 100644 --- a/components/definitions/chromel.d.ts +++ b/components/definitions/chromel.d.ts @@ -199,7 +199,7 @@ declare namespace chrome.braveShields { const setNoScriptControlTypeAsync: any const getNoScriptControlTypeAsync: any - const hostnameCosmeticResources: (hostname: string, callback: (stylesheet: string, genericExceptions: string[], scriptInjections: string[]) => void) => void + 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 = { From 2951ab4f7a3cd0173bd2817909257c2e576b477b Mon Sep 17 00:00:00 2001 From: Snuupy Date: Tue, 18 Jun 2019 12:58:02 -0700 Subject: [PATCH 3/6] mutation observer applied only if rule exists notes change order mutation observer WIP browser tests isIdempotent isIdempotent mutation observer mutationObserver mutation observer mutationObserver mutation observer commented mutation observer for anton cleanup comments --- ..._content_settings_registry_browsertest2.cc | 46 ++++++++ components/brave_extension/adbox.html | 27 +++++ .../brave_extension_browsertest.cc | 36 +++++++ .../background/api/cosmeticFilterAPI.ts | 54 ++++++++-- .../reducers/cosmeticFilterReducer.ts | 23 ++-- .../extension/brave_extension/content.ts | 101 ++++++++++++++++++ .../extension/brave_extension/notes.md | 0 .../extension/brave_extension_browsertest.cc | 71 ++++++++++++ components/brave_extension/index.html | 11 ++ components/brave_extension/index.js | 18 ++++ .../brave_extension/mutationObserver.html | 12 +++ test/BUILD.gn | 2 + .../cosmetic-filter/mutation_observer.html | 11 ++ .../data/cosmetic-filter/mutation_observer.js | 18 ++++ 14 files changed, 418 insertions(+), 12 deletions(-) create mode 100644 chromium_src/components/content_settings/core/browser/brave_content_settings_registry_browsertest2.cc create mode 100644 components/brave_extension/adbox.html create mode 100644 components/brave_extension/brave_extension_browsertest.cc create mode 100644 components/brave_extension/extension/brave_extension/notes.md create mode 100644 components/brave_extension/extension/brave_extension_browsertest.cc create mode 100644 components/brave_extension/index.html create mode 100644 components/brave_extension/index.js create mode 100644 components/brave_extension/mutationObserver.html create mode 100644 test/data/cosmetic-filter/mutation_observer.html create mode 100644 test/data/cosmetic-filter/mutation_observer.js 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/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/background/api/cosmeticFilterAPI.ts b/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts index 936e1453efe6..f11242749f36 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,7 @@ export const removeSiteFilter = (origin: string) => { }) } -export const applySiteFilters = (tabId: number, hostname: string) => { +export const applyCSSCosmeticFilters = (tabId: number, hostname: string) => { chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { if (!storeData.cosmeticFilterList) { if (process.env.NODE_ENV === 'shields_development') { @@ -31,12 +39,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 +56,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/reducers/cosmeticFilterReducer.ts b/components/brave_extension/extension/brave_extension/background/reducers/cosmeticFilterReducer.ts index f42ae60cd981..54b5e4392345 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)) } - 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) break } } diff --git a/components/brave_extension/extension/brave_extension/content.ts b/components/brave_extension/extension/brave_extension/content.ts index a1c3d55d2388..5313e759fa7f 100644 --- a/components/brave_extension/extension/brave_extension/content.ts +++ b/components/brave_extension/extension/brave_extension/content.ts @@ -1,10 +1,111 @@ const unique = require('unique-selector').default +//import { debounce } from '../../../common/debounce' + let target: EventTarget | null +let contentSiteFilters: any + +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 { + //applyDOMCosmeticFilterDebounce(contentSiteFilters) + 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 (filterList: any) { + console.log('applyDOMCosmeticFilterDebounce call') + let targetNode = document.documentElement + let observer = new MutationObserver(function (mutations) { + console.log('mutation observed') + injectIncrementalStyles(mutations) + }) + let observerConfig = { + childList: true, + subtree: true + // characterData: true + } + observer.observe(targetNode, observerConfig) +}*/ + +/*function removeAll (siteFilters: any) { + // array of site filters, go through each one and check if idempotent/already applied + if (siteFilters) { + siteFilters.map((filterData: 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 + } + ] +*/ + +// MutationObserver(applyDOMCosmeticFilters()) + document.addEventListener('contextmenu', (event) => { // send host and store target // `target` needed for when background page handles `addBlockElement` 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_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/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) +} From 6a1c93f08bcd60b445090df3c88b3fefabca75ec Mon Sep 17 00:00:00 2001 From: Anton Lazarev Date: Tue, 20 Aug 2019 16:20:52 -0700 Subject: [PATCH 4/6] use cosmetic filtering api --- .../background/api/cosmeticFilterAPI.ts | 18 +++ .../background/events/cosmeticFilterEvents.ts | 10 ++ .../extension/brave_extension/content.ts | 122 ++++++++++++++---- 3 files changed, 128 insertions(+), 22 deletions(-) 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 f11242749f36..279c675c812d 100644 --- a/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts +++ b/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts @@ -31,6 +31,24 @@ export const removeSiteFilter = (origin: string) => { } export const applyCSSCosmeticFilters = (tabId: number, hostname: string) => { + 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') { 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/content.ts b/components/brave_extension/extension/brave_extension/content.ts index 5313e759fa7f..c5857733f628 100644 --- a/components/brave_extension/extension/brave_extension/content.ts +++ b/components/brave_extension/extension/brave_extension/content.ts @@ -3,6 +3,8 @@ const unique = require('unique-selector').default let target: EventTarget | null let contentSiteFilters: any +let genericExceptions: Array | undefined = undefined +let classIdBuffer: { classes: string[], ids: string[] } = { classes: [], ids: [] } if (process.env.NODE_ENV === 'development') { console.info('development content script here') @@ -44,7 +46,6 @@ chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { // fetch fi } else if (Object.keys(storeData.cosmeticFilterList).length === 0) { console.log('storeData.cosmeticFilterList length === 0') } else { - //applyDOMCosmeticFilterDebounce(contentSiteFilters) console.log('ON COMMITTED MUTATION OBSERVER BEING APPLIED:') // removeAll(contentSiteFilters) chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { // fetch filter list @@ -53,12 +54,17 @@ chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { // fetch fi } }) -/*function applyDOMCosmeticFilterDebounce (filterList: any) { +function applyDOMCosmeticFilterDebounce() { console.log('applyDOMCosmeticFilterDebounce call') let targetNode = document.documentElement - let observer = new MutationObserver(function (mutations) { - console.log('mutation observed') - injectIncrementalStyles(mutations) + 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, @@ -66,25 +72,24 @@ chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { // fetch fi // characterData: true } observer.observe(targetNode, observerConfig) -}*/ +} -/*function removeAll (siteFilters: any) { +/*const injectIncrementalStyles = (mutations: MutationRecord[]) => { // array of site filters, go through each one and check if idempotent/already applied - if (siteFilters) { - siteFilters.map((filterData: 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) + + 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 = [ @@ -104,7 +109,68 @@ chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { // fetch fi ] */ -// MutationObserver(applyDOMCosmeticFilters()) +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 @@ -122,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) + } } }) From ab3434127f9b2b5adc607bce412efceb2576b0de Mon Sep 17 00:00:00 2001 From: Anton Lazarev Date: Fri, 23 Aug 2019 16:35:14 -0700 Subject: [PATCH 5/6] add per-site toggleable cosmetic blocking preference --- browser/extensions/api/brave_shields_api.cc | 40 ++++++++++++++++++ browser/extensions/api/brave_shields_api.h | 20 +++++++++ common/extensions/api/brave_shields.json | 42 +++++++++++++++++++ .../browser/brave_shields_util.cc | 24 +++++++++++ .../browser/brave_shields_util.h | 3 ++ .../common/brave_shield_constants.h | 1 + components/definitions/chromel.d.ts | 2 + 7 files changed, 132 insertions(+) diff --git a/browser/extensions/api/brave_shields_api.cc b/browser/extensions/api/brave_shields_api.cc index 26fd3deee063..23450941b8d4 100644 --- a/browser/extensions/api/brave_shields_api.cc +++ b/browser/extensions/api/brave_shields_api.cc @@ -129,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 4170fd773c75..aad9c8c78605 100644 --- a/browser/extensions/api/brave_shields_api.h +++ b/browser/extensions/api/brave_shields_api.h @@ -63,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/common/extensions/api/brave_shields.json b/common/extensions/api/brave_shields.json index 26f1ba3ce7fe..e2d02a865877 100644 --- a/common/extensions/api/brave_shields.json +++ b/common/extensions/api/brave_shields.json @@ -132,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_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 a3c191737135..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 From d224495f3fa8b5f7263c504713cfab18b9869f27 Mon Sep 17 00:00:00 2001 From: Anton Lazarev Date: Mon, 26 Aug 2019 13:39:01 -0700 Subject: [PATCH 6/6] add cosmetic filtering toggle in shields panel --- .../_locales/en_US/messages.json | 4 + .../actions/shieldsPanelActions.ts | 7 ++ .../background/api/cosmeticFilterAPI.ts | 34 +++++---- .../background/api/shieldsAPI.ts | 24 ++++-- .../reducers/cosmeticFilterReducer.ts | 4 +- .../reducers/shieldsPanelReducer.ts | 22 ++++++ .../controls/cosmeticBlockingControl.tsx | 75 +++++++++++++++++++ .../components/advancedView/index.tsx | 5 ++ .../advancedView/interfaceControls.tsx | 15 +++- .../components/braveShields.tsx | 2 + .../constants/shieldsPanelTypes.ts | 1 + .../types/actions/shieldsPanelActions.ts | 10 +++ .../types/constants/shieldsPanelTypes.ts | 1 + .../types/state/shieldsPannelState.ts | 1 + 14 files changed, 179 insertions(+), 26 deletions(-) create mode 100644 components/brave_extension/extension/brave_extension/components/advancedView/controls/cosmeticBlockingControl.tsx 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 279c675c812d..ef37bb9098b6 100644 --- a/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts +++ b/components/brave_extension/extension/brave_extension/background/api/cosmeticFilterAPI.ts @@ -30,24 +30,26 @@ export const removeSiteFilter = (origin: string) => { }) } -export const applyCSSCosmeticFilters = (tabId: number, hostname: string) => { - chrome.braveShields.hostnameCosmeticResources(hostname, (stylesheet, genericExceptions, injectedScript) => { - chrome.tabs.insertCSS(tabId, { - code: stylesheet, - cssOrigin: 'user', - runAt: 'document_start' - }) +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.sendMessage(tabId, { + type: 'cosmeticFilterGenericExceptions', + exceptions: genericExceptions + }) - chrome.tabs.executeScript(tabId, { - code: injectedScript, - runAt: 'document_start' - }); - }) + chrome.tabs.executeScript(tabId, { + code: injectedScript, + runAt: 'document_start' + }); + }) + } chrome.storage.local.get('cosmeticFilterList', (storeData = {}) => { if (!storeData.cosmeticFilterList) { 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/reducers/cosmeticFilterReducer.ts b/components/brave_extension/extension/brave_extension/background/reducers/cosmeticFilterReducer.ts index 54b5e4392345..6025c57b352c 100644 --- a/components/brave_extension/extension/brave_extension/background/reducers/cosmeticFilterReducer.ts +++ b/components/brave_extension/extension/brave_extension/background/reducers/cosmeticFilterReducer.ts @@ -70,7 +70,7 @@ 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)) + applyCSSCosmeticFilters(action.tabId, getHostname(action.url), tabData.cosmeticBlocking === "block") } break } @@ -160,7 +160,7 @@ export default function cosmeticFilterReducer ( .catch(e => { console.error('Could not add filter:', e) }) - applyCSSCosmeticFilters(tabId, tabData.hostname) + 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 ( <> +