diff --git a/app/brave_settings_strings.grdp b/app/brave_settings_strings.grdp index 3cafe6486582..1661b8ebb0f0 100644 --- a/app/brave_settings_strings.grdp +++ b/app/brave_settings_strings.grdp @@ -577,6 +577,51 @@ Filter lists + + Developer mode + + + Allow adding custom filters and scriptlets + + + Custom scriptlets + + + Add new scriptlet + + + Add new scriptlet + + + Edit scriptlet + + + Name + + + Content + + + Cancel + + + Save + + + Are you sure you want to delete this scriptlet? If this scriptlet is used in any custom filter, it will no longer work. + + + Scriptlet already exists. + + + Invalid scriptlet name. + + + Scriptlet is not found. + + + Don’t paste code here that you don’t understand or haven’t reviewed yourself. This could allow attackers to steal your identity or take control of your computer. + Shortcuts diff --git a/browser/about_flags.cc b/browser/about_flags.cc index 362447e1f43e..00a26143ff7b 100644 --- a/browser/about_flags.cc +++ b/browser/about_flags.cc @@ -452,6 +452,16 @@ FEATURE_VALUE_TYPE(kExtensionsManifestV2), \ })) +#define BRAVE_ADBLOCK_CUSTOM_SCRIPTLETS \ + EXPAND_FEATURE_ENTRIES({ \ + "brave-adblock-custom-scriptlets", \ + "Brave Adblock Custom Scriptlets", \ + "Allows adding custom scriptlets from settings", \ + kOsDesktop | kOsAndroid, \ + FEATURE_VALUE_TYPE( \ + brave_shields::features::kCosmeticFilteringCustomScriptlets), \ + }) + // Keep the last item empty. #define LAST_BRAVE_FEATURE_ENTRIES_ITEM @@ -994,6 +1004,7 @@ BRAVE_MIDDLE_CLICK_AUTOSCROLL_FEATURE_ENTRY \ BRAVE_EXTENSIONS_MANIFEST_V2 \ BRAVE_WORKAROUND_NEW_WINDOW_FLASH \ + BRAVE_ADBLOCK_CUSTOM_SCRIPTLETS \ LAST_BRAVE_FEATURE_ENTRIES_ITEM // Keep it as the last item. namespace flags_ui { namespace { diff --git a/browser/brave_profile_prefs.cc b/browser/brave_profile_prefs.cc index 23bf54eaf2d7..81a45ec7ee39 100644 --- a/browser/brave_profile_prefs.cc +++ b/browser/brave_profile_prefs.cc @@ -256,6 +256,8 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { true); registry->RegisterBooleanPref(brave_shields::prefs::kLinkedInEmbedControlType, false); + registry->RegisterBooleanPref(brave_shields::prefs::kAdBlockDeveloperMode, + false); // WebTorrent #if BUILDFLAG(ENABLE_BRAVE_WEBTORRENT) diff --git a/browser/brave_shields/ad_block_custom_resources_browsertest.cc b/browser/brave_shields/ad_block_custom_resources_browsertest.cc new file mode 100644 index 000000000000..4a0ec3cec46b --- /dev/null +++ b/browser/brave_shields/ad_block_custom_resources_browsertest.cc @@ -0,0 +1,259 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "base/base64.h" +#include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" +#include "base/values.h" +#include "brave/browser/brave_browser_process.h" +#include "brave/browser/brave_shields/ad_block_service_browsertest.h" +#include "brave/browser/ui/webui/brave_settings_ui.h" +#include "brave/components/brave_shields/content/browser/ad_block_service.h" +#include "brave/components/brave_shields/core/browser/ad_block_custom_resource_provider.h" +#include "brave/components/brave_shields/core/common/features.h" +#include "brave/components/brave_shields/core/common/pref_names.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "components/prefs/pref_service.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "url/gurl.h" + +namespace { + +void AwaitElement(content::WebContents* web_contents, + const std::string& root, + const std::string& id) { + constexpr const char kScript[] = R"js( + (async () => { + while (!window.testing[$1].getElementById($2)) { + await new Promise(r => setTimeout(r, 10)); + } + return true; + })(); + )js"; + EXPECT_TRUE( + content::EvalJs(web_contents, content::JsReplace(kScript, root, id)) + .ExtractBool()); +} + +bool ClickAddCustomScriptlet(content::WebContents* web_contents) { + AwaitElement(web_contents, "adblockScriptletList", "add-custom-scriptlet"); + return EvalJs(web_contents, + "window.testing.adblockScriptletList.getElementById('add-" + "custom-scriptlet').click()") + .value.is_none(); +} + +bool SetCustomScriptletValue(content::WebContents* web_contents, + const std::string& id, + const std::string& value) { + AwaitElement(web_contents, "adblockScriptletEditor", id); + constexpr const char kSetValue[] = R"js( + (function() { + const e = window.testing.adblockScriptletEditor.getElementById($1); + e.value = $2; + const event = new Event('input', {bubbles: true}); + event.simulated = true; + return e.dispatchEvent(event); + })(); + )js"; + return EvalJs(web_contents, content::JsReplace(kSetValue, id, value)) + .value.GetBool(); +} + +bool SetCustomScriptletName(content::WebContents* web_contents, + const std::string& name) { + return SetCustomScriptletValue(web_contents, "scriptlet-name", name); +} + +bool SetCustomScriptletContent(content::WebContents* web_contents, + const std::string& content) { + return SetCustomScriptletValue(web_contents, "scriptlet-content", content); +} + +std::string GetCustomScriptletValue(content::WebContents* web_contents, + const std::string& id) { + AwaitElement(web_contents, "adblockScriptletEditor", id); + return EvalJs(web_contents, + "window.testing.adblockScriptletEditor.getElementById('" + id + + "').value") + .value.GetString(); +} + +std::string GetCustomScriptletName(content::WebContents* web_contents) { + return GetCustomScriptletValue(web_contents, "scriptlet-name"); +} + +std::string GetCustomScriptletContent(content::WebContents* web_contents) { + return GetCustomScriptletValue(web_contents, "scriptlet-content"); +} + +bool ClickSaveCustomScriptlet(content::WebContents* web_contents) { + AwaitElement(web_contents, "adblockScriptletEditor", "save"); + return EvalJs(web_contents, + "window.testing.adblockScriptletEditor.getElementById('save')." + "click()") + .value.is_none(); +} + +bool ClickCustomScriplet(content::WebContents* web_contents, + const std::string& name, + const std::string& button) { + AwaitElement(web_contents, "adblockScriptletList", name); + constexpr const char kClick[] = R"js( + (function() { + const e = window.testing.adblockScriptletList.getElementById($1); + const b = e.querySelector($2); + b.click(); + })(); + )js"; + return EvalJs(web_contents, content::JsReplace(kClick, name, "#" + button)) + .value.is_none(); +} + +} // namespace + +class AdblockCustomResourcesTest : public AdBlockServiceTest { + public: + AdblockCustomResourcesTest() { + feature_list_.InitAndEnableFeature( + brave_shields::features::kCosmeticFilteringCustomScriptlets); + BraveSettingsUI::ShouldExposeElementsForTesting() = true; + } + + ~AdblockCustomResourcesTest() override { + BraveSettingsUI::ShouldExposeElementsForTesting() = true; + } + + void SaveCustomScriptlet(const std::string& name, const std::string& value) { + ASSERT_EQ(GURL("chrome://settings/shields/filters"), + web_contents()->GetLastCommittedURL()); + + ASSERT_TRUE(SetCustomScriptletContent(web_contents(), value)); + ASSERT_TRUE(SetCustomScriptletName(web_contents(), name)); + ASSERT_TRUE(ClickSaveCustomScriptlet(web_contents())); + } + + void CheckCustomScriptlet(const base::Value& custom_scriptlet, + const std::string& name, + const std::string& content) { + ASSERT_TRUE(custom_scriptlet.is_dict()); + EXPECT_EQ(name, *custom_scriptlet.GetDict().FindString("name")); + EXPECT_EQ(base::Base64Encode(content), + *custom_scriptlet.GetDict().FindString("content")); + EXPECT_EQ("application/javascript", + *custom_scriptlet.GetDict().FindStringByDottedPath("kind.mime")); + } + + base::Value GetCustomResources() { + base::RunLoop loop; + base::Value result; + g_brave_browser_process->ad_block_service() + ->custom_resource_provider() + ->GetCustomResources( + base::BindLambdaForTesting([&loop, &result](base::Value resources) { + result = std::move(resources); + loop.Quit(); + })); + loop.Run(); + return result; + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(AdblockCustomResourcesTest, AddEditRemoveScriptlet) { + EnableDeveloperMode(true); + + NavigateToURL(GURL("brave://settings/shields/filters")); + + constexpr const char kContent[] = "window.test = 'custom-script'"; + + ASSERT_TRUE(ClickAddCustomScriptlet(web_contents())); + SaveCustomScriptlet("custom-script", kContent); + + { + const auto& custom_resources = GetCustomResources(); + ASSERT_TRUE(custom_resources.is_list()); + ASSERT_EQ(1u, custom_resources.GetList().size()); + CheckCustomScriptlet(custom_resources.GetList().front(), + "user-custom-script.js", kContent); + } + + constexpr const char kEditedContent[] = "window.test = 'edited'"; + + ASSERT_TRUE( + ClickCustomScriplet(web_contents(), "user-custom-script.js", "edit")); + + EXPECT_EQ("user-custom-script.js", GetCustomScriptletName(web_contents())); + EXPECT_EQ(kContent, GetCustomScriptletContent(web_contents())); + SaveCustomScriptlet("custom-script-edited", kEditedContent); + { + const auto& custom_resources = GetCustomResources(); + ASSERT_TRUE(custom_resources.is_list()); + ASSERT_EQ(1u, custom_resources.GetList().size()); + CheckCustomScriptlet(custom_resources.GetList().front(), + "user-custom-script-edited.js", kEditedContent); + } + + ASSERT_TRUE(ClickCustomScriplet(web_contents(), + "user-custom-script-edited.js", "delete")); + { + const auto& custom_resources = GetCustomResources(); + ASSERT_TRUE(custom_resources.is_list()); + ASSERT_TRUE(custom_resources.GetList().empty()); + } +} + +IN_PROC_BROWSER_TEST_F(AdblockCustomResourcesTest, ExecCustomScriptlet) { + EnableDeveloperMode(true); + + NavigateToURL(GURL("brave://settings/shields/filters")); + + constexpr const char kContent[] = "window.test = 'custom-script'"; + + ASSERT_TRUE(ClickAddCustomScriptlet(web_contents())); + SaveCustomScriptlet("custom-script", kContent); + + UpdateAdBlockInstanceWithRules("a.com##+js(user-custom-script)"); + + GURL tab_url = + embedded_test_server()->GetURL("a.com", "/cosmetic_filtering.html"); + NavigateToURL(tab_url); + + EXPECT_EQ("custom-script", EvalJs(web_contents(), "window.test")); +} + +IN_PROC_BROWSER_TEST_F(AdblockCustomResourcesTest, NameConflicts) { + EnableDeveloperMode(true); + + constexpr const char kBraveFix[] = "window.test = 'default-script'"; + constexpr const char kBraveFixResource[] = R"json( + [{ + "name": "user-fix.js", + "kind": { "mime": "application/javascript" }, + "content": "$1" + }] + )json"; + + UpdateAdBlockResources(base::ReplaceStringPlaceholders( + kBraveFixResource, {base::Base64Encode(kBraveFix)}, nullptr)); + + NavigateToURL(GURL("brave://settings/shields/filters")); + + constexpr const char kContent[] = "window.test = 'custom-script'"; + + ASSERT_TRUE(ClickAddCustomScriptlet(web_contents())); + SaveCustomScriptlet("user-fix", kContent); + + UpdateAdBlockInstanceWithRules("a.com##+js(user-fix)"); + + GURL tab_url = + embedded_test_server()->GetURL("a.com", "/cosmetic_filtering.html"); + NavigateToURL(tab_url); + + EXPECT_EQ("default-script", EvalJs(web_contents(), "window.test")); +} diff --git a/browser/brave_shields/ad_block_pref_service_factory.cc b/browser/brave_shields/ad_block_pref_service_factory.cc index fdba7c933fd4..3b67787c795d 100644 --- a/browser/brave_shields/ad_block_pref_service_factory.cc +++ b/browser/brave_shields/ad_block_pref_service_factory.cc @@ -11,6 +11,7 @@ #include "base/no_destructor.h" #include "brave/browser/brave_browser_process.h" #include "brave/components/brave_shields/content/browser/ad_block_pref_service.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/net/proxy_service_factory.h" #include "chrome/browser/profiles/incognito_helpers.h" #include "chrome/browser/profiles/profile.h" @@ -47,7 +48,8 @@ AdBlockPrefServiceFactory::BuildServiceInstanceForBrowserContext( Profile* profile = Profile::FromBrowserContext(context); auto service = std::make_unique( - g_brave_browser_process->ad_block_service(), profile->GetPrefs()); + g_brave_browser_process->ad_block_service(), profile->GetPrefs(), + g_browser_process->local_state()); auto pref_proxy_config_tracker = ProxyServiceFactory::CreatePrefProxyConfigTrackerOfProfile( diff --git a/browser/brave_shields/ad_block_service_browsertest.cc b/browser/brave_shields/ad_block_service_browsertest.cc index 672513999b4e..c34c88e79915 100644 --- a/browser/brave_shields/ad_block_service_browsertest.cc +++ b/browser/brave_shields/ad_block_service_browsertest.cc @@ -251,9 +251,7 @@ void AdBlockServiceTest::UpdateAdBlockResources(const std::string& resources) { brave_shields::AdBlockService* service = g_brave_browser_process->ad_block_service(); - static_cast( - service->resource_provider()) - ->OnComponentReady(component_path); + service->default_resource_provider()->OnComponentReady(component_path); } void AdBlockServiceTest::UpdateAdBlockInstanceWithRules( @@ -276,6 +274,11 @@ void AdBlockServiceTest::UpdateAdBlockInstanceWithRules( engine_observer.Wait(); } +void AdBlockServiceTest::EnableDeveloperMode(bool enabled) { + profile()->GetPrefs()->SetBoolean(brave_shields::prefs::kAdBlockDeveloperMode, + enabled); +} + void AdBlockServiceTest::UpdateCustomAdBlockInstanceWithRules( const std::string& rules) { brave_shields::AdBlockService* ad_block_service = @@ -314,7 +317,7 @@ base::FilePath AdBlockServiceTest::GetTestDataDir() { return base::PathService::CheckedGet(brave::DIR_TEST_DATA); } -void AdBlockServiceTest::NavigateToURL(GURL url) { +void AdBlockServiceTest::NavigateToURL(const GURL& url) { content::NavigateToURLBlockUntilNavigationsComplete(web_contents(), url, 1, true); } diff --git a/browser/brave_shields/ad_block_service_browsertest.h b/browser/brave_shields/ad_block_service_browsertest.h index f5aa04122996..b4e3c9f7b869 100644 --- a/browser/brave_shields/ad_block_service_browsertest.h +++ b/browser/brave_shields/ad_block_service_browsertest.h @@ -49,11 +49,12 @@ class AdBlockServiceTest : public PlatformBrowserTest { bool first_party_protections = false); void UpdateAdBlockResources(const std::string& resources); void UpdateAdBlockInstanceWithRules(const std::string& rules); + void EnableDeveloperMode(bool enabled); void UpdateCustomAdBlockInstanceWithRules(const std::string& rules); void AssertTagExists(const std::string& tag, bool expected_exists) const; void InitEmbeddedTestServer(); base::FilePath GetTestDataDir(); - void NavigateToURL(GURL url); + void NavigateToURL(const GURL& url); void SetDefaultComponentIdAndBase64PublicKeyForTest(); void SetRegionalComponentIdAndBase64PublicKeyForTest(); void InstallComponent( diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index c3fc2d0ca927..d928a4d7da44 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -100,6 +100,8 @@ const PrefsUtil::TypedPrefMap& BravePrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::kBoolean; (*s_brave_allowlist)[brave_shields::prefs::kReduceLanguageEnabled] = settings_api::PrefType::kBoolean; + (*s_brave_allowlist)[brave_shields::prefs::kAdBlockDeveloperMode] = + settings_api::PrefType::kBoolean; // Webcompat Reporter (*s_brave_allowlist)[webcompat_reporter::prefs::kContactInfoSaveFlagPrefs] = diff --git a/browser/resources/settings/default_brave_shields_page/brave_adblock_browser_proxy.ts b/browser/resources/settings/default_brave_shields_page/brave_adblock_browser_proxy.ts index df24e2c75d90..79aaebdd47aa 100644 --- a/browser/resources/settings/default_brave_shields_page/brave_adblock_browser_proxy.ts +++ b/browser/resources/settings/default_brave_shields_page/brave_adblock_browser_proxy.ts @@ -5,7 +5,22 @@ // @ts-nocheck TODO(petemill): Define types and remove ts-nocheck -import { sendWithPromise, addWebUiListener } from 'chrome://resources/js/cr.js'; +import { sendWithPromise, addWebUiListener } from 'chrome://resources/js/cr.js' + +export class Scriptlet { + name: string + kind: object = { + mime: 'application/javascript' + } + content: string +} + +export enum ErrorCode { + kOK = 0, + kInvalidName, + kAlreadyExists, + kNotFound, +} export interface BraveAdblockBrowserProxy { getRegionalLists(): Promise // TODO(petemill): Define the expected type @@ -19,6 +34,10 @@ export interface BraveAdblockBrowserProxy { updateSubscription(url: string) deleteSubscription(url: string) viewSubscription(url: string) + getCustomScriptlets(): Promise + addCustomScriptlet(scriptlet: Scriptlet): Promise + updateCustomScriptlet(name: string, scriptlet: Scriptlet): Promise + removeCustomScriptlet(name: string): Promise } export class BraveAdblockBrowserProxyImpl implements BraveAdblockBrowserProxy { @@ -74,7 +93,53 @@ export class BraveAdblockBrowserProxyImpl implements BraveAdblockBrowserProxy { chrome.send('brave_adblock.viewSubscription', [url]) } - addWebUiListener (event_name, callback) { + utf8ToBase64_(str) { + const uint8Array = new TextEncoder().encode(str) + const base64String = btoa(String.fromCharCode.apply(null, uint8Array)) + return base64String + } + + base64ToUtf8_(base64) { + const binaryString = atob(base64) + const bytes = new Uint8Array(binaryString.length) + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i) + } + return new TextDecoder().decode(bytes) + } + + getCustomScriptlets() { + return sendWithPromise('brave_adblock.getCustomScriptlets') + .then((scriptlets) => { + for (const scriptlet of scriptlets) { + scriptlet.content = this.base64ToUtf8_(scriptlet.content) + } + return scriptlets + }) + .catch((error) => { + throw error + }) + } + + addCustomScriptlet(scriptlet) { + scriptlet.content = this.utf8ToBase64_(scriptlet.content) + return sendWithPromise('brave_adblock.addCustomScriptlet', scriptlet) + } + + updateCustomScriptlet(name, scriptlet) { + scriptlet.content = this.utf8ToBase64_(scriptlet.content) + return sendWithPromise( + 'brave_adblock.updateCustomScriptlet', + name, + scriptlet + ) + } + + removeCustomScriptlet(name) { + return sendWithPromise('brave_adblock.removeCustomScriptlet', name) + } + + addWebUiListener(event_name, callback) { addWebUiListener(event_name, callback) } } diff --git a/browser/resources/settings/default_brave_shields_page/brave_adblock_subpage.html b/browser/resources/settings/default_brave_shields_page/brave_adblock_subpage.html index 2f6a04deb9a8..d7fcf69f3374 100644 --- a/browser/resources/settings/default_brave_shields_page/brave_adblock_subpage.html +++ b/browser/resources/settings/default_brave_shields_page/brave_adblock_subpage.html @@ -316,6 +316,14 @@ + + + + @@ -323,8 +331,20 @@ + + + + + + $i18n{adblockCustomSciptletsListLabel} + + + + + diff --git a/browser/resources/settings/default_brave_shields_page/brave_adblock_subpage.ts b/browser/resources/settings/default_brave_shields_page/brave_adblock_subpage.ts index 5660ae8d40d1..001a79a52e49 100644 --- a/browser/resources/settings/default_brave_shields_page/brave_adblock_subpage.ts +++ b/browser/resources/settings/default_brave_shields_page/brave_adblock_subpage.ts @@ -9,6 +9,7 @@ import 'chrome://resources/cr_elements/cr_button/cr_button.js'; import 'chrome://resources/cr_elements/icons.html.js'; import './components/brave_adblock_subscribe_dropdown.js'; import './components/brave_adblock_editor.js'; +import './components/brave_adblock_scriptlet_list.js'; import {PrefsMixin} from '/shared/settings/prefs/prefs_mixin.js'; import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js'; @@ -19,6 +20,10 @@ import {BaseMixin} from '../base_mixin.js'; import {BraveAdblockBrowserProxyImpl} from './brave_adblock_browser_proxy.js' import {getTemplate} from './brave_adblock_subpage.html.js' +import { loadTimeData } from '../i18n_setup.js' + +import type {SettingsToggleButtonElement} from '../controls/settings_toggle_button.js'; + const AdBlockSubpageBase = PrefsMixin(I18nMixin(BaseMixin(PolymerElement))) class AdBlockSubpage extends AdBlockSubpageBase { @@ -41,6 +46,12 @@ class AdBlockSubpage extends AdBlockSubpageBase { type: Boolean, value: false }, + cosmeticFilteringCustomScriptletsEnabled_: { + type: Boolean, + value: loadTimeData.getBoolean( + 'cosmeticFilteringCustomScriptletsEnabled' + ) + } } } diff --git a/browser/resources/settings/default_brave_shields_page/components/brave_adblock_editor.html b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_editor.html index 7d6ed14cb8e9..421e149be115 100644 --- a/browser/resources/settings/default_brave_shields_page/components/brave_adblock_editor.html +++ b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_editor.html @@ -49,6 +49,7 @@ - + $i18n{adblockSaveChangesButtonLabel} diff --git a/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_editor.html b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_editor.html new file mode 100644 index 000000000000..f23868f1dc23 --- /dev/null +++ b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_editor.html @@ -0,0 +1,57 @@ + + + + [[dialogTitle_]] + + + $i18n{adblockCustomSciptletDialogNameLabel} + + + + + $i18n{adblockCustomScriptletDialogContentLabel} + + + + + + $i18n{adblockCustomScriptletDialogCancelButton} + + + $i18n{adblockCustomScriptletDialogSaveButton} + + + \ No newline at end of file diff --git a/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_editor.ts b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_editor.ts new file mode 100644 index 000000000000..e43b8fdaedcc --- /dev/null +++ b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_editor.ts @@ -0,0 +1,150 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +import 'chrome://resources/cr_elements/cr_button/cr_button.js' +import { CrDialogElement } from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js' +import 'chrome://resources/cr_elements/cr_input/cr_input.js' + +import { PrefsMixin } from '/shared/settings/prefs/prefs_mixin.js' +import { I18nMixin } from 'chrome://resources/cr_elements/i18n_mixin.js' +import { PolymerElement } from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js' + +import { getTemplate } from './brave_adblock_scriptlet_editor.html.js' + +import { loadTimeData } from '../../i18n_setup.js' + +import { + Scriptlet, + BraveAdblockBrowserProxyImpl, + ErrorCode +} from '../brave_adblock_browser_proxy.js' + +interface AdblockScriptletEditor { + $: { + dialog: CrDialogElement + } +} + +const AdblockScriptletEditorBase = I18nMixin(PrefsMixin(PolymerElement)) + +class AdblockScriptletEditor extends AdblockScriptletEditorBase { + static get is() { + return 'adblock-scriptlet-editor' + } + + static get template() { + return getTemplate() + } + + static get properties() { + return { + scriptlet: Scriptlet, + dialogTitle_: String, + isScriptletValid_: Boolean, + scriptletErrorMessage_: String + } + } + + scriptlet: Scriptlet + dialogTitle_: string + isScriptletValid_: boolean + scriptletErrorMessage_: string + + oldScriptletName_: string + browserProxy_ = BraveAdblockBrowserProxyImpl.getInstance() + + override ready() { + super.ready() + if (loadTimeData.getBoolean('shouldExposeElementsForTesting')) { + window.testing = window.testing || {} + window.testing[`adblockScriptletEditor`] = this.shadowRoot + } + + this.oldScriptletName_ = this.scriptlet.name + + if (this.oldScriptletName_) { + this.dialogTitle_ = this.i18n('adblockEditCustomScriptletDialogTitle') + } else { + this.dialogTitle_ = this.i18n('adblockAddCustomScriptletDialogTitle') + } + + this.updateError(ErrorCode.kOK) + } + + updateError(error_code: ErrorCode) { + this.isScriptletValid_ = error_code === ErrorCode.kOK + switch (error_code) { + case ErrorCode.kOK: + this.scriptletErrorMessage_ = '' + break + case ErrorCode.kAlreadyExists: + this.scriptletErrorMessage_ = this.i18n( + 'adblockCustomScriptletAlreadyExistsError' + ) + break + case ErrorCode.kInvalidName: + this.scriptletErrorMessage_ = this.i18n( + 'adblockCustomScriptletInvalidNameError' + ) + break + case ErrorCode.kNotFound: + this.scriptletErrorMessage_ = this.i18n( + 'adblockCustomScriptletNotFoundError' + ) + break + } + } + + cancelClicked_() { + this.$.dialog.cancel() + } + + saveClicked_() { + this.updateScriptletBeforeSave_() + if (!this.isScriptletValid_) { + return + } + + if (this.oldScriptletName_) { + this.browserProxy_ + .updateCustomScriptlet(this.oldScriptletName_, this.scriptlet) + .then((e) => { + this.updateError(e) + if (this.isScriptletValid_) { + this.$.dialog.close() + } + }) + } else { + this.browserProxy_.addCustomScriptlet(this.scriptlet).then((e) => { + this.updateError(e) + if (this.isScriptletValid_) { + this.$.dialog.close() + } + }) + } + } + + validateName_() { + this.scriptlet.name = this.scriptlet.name.toLowerCase() + if (!/^[a-zA-Z0-9-_.]+$/.test(this.scriptlet.name)) { + this.updateError(ErrorCode.kInvalidName) + } else { + this.updateError(ErrorCode.kOK) + } + } + + updateScriptletBeforeSave_() { + this.scriptlet.name = this.scriptlet.name.toLowerCase() + if (!this.scriptlet.name.startsWith('user-')) { + this.scriptlet.name = 'user-' + this.scriptlet.name + } + if (!this.scriptlet.name.endsWith('.js')) { + this.scriptlet.name = this.scriptlet.name + '.js' + } + this.validateName_() + } +} + +customElements.define(AdblockScriptletEditor.is, AdblockScriptletEditor) diff --git a/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_list.html b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_list.html new file mode 100644 index 000000000000..e818591a9c3c --- /dev/null +++ b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_list.html @@ -0,0 +1,52 @@ + + + + + + + + [[item.name]] + + + + + + + + + + + + + $i18n{adblockAddCustomScriptletButton} + + + + + + + \ No newline at end of file diff --git a/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_list.ts b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_list.ts new file mode 100644 index 000000000000..776088c5b691 --- /dev/null +++ b/browser/resources/settings/default_brave_shields_page/components/brave_adblock_scriptlet_list.ts @@ -0,0 +1,120 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import 'chrome://resources/cr_elements/cr_button/cr_button.js' +import 'chrome://resources/cr_elements/icons.html.js' + +import { PrefsMixin } from '/shared/settings/prefs/prefs_mixin.js' +import { I18nMixin } from 'chrome://resources/cr_elements/i18n_mixin.js' +import { PolymerElement } from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js' + +import { BaseMixin } from '../../base_mixin.js' + +import { getTemplate } from './brave_adblock_scriptlet_list.html.js' + +import { loadTimeData } from '../../i18n_setup.js' + +import { + Scriptlet, + BraveAdblockBrowserProxyImpl +} from '../brave_adblock_browser_proxy.js' + +import './brave_adblock_scriptlet_editor.js' + +const AdblockScriptletListBase = PrefsMixin( + I18nMixin(BaseMixin(PolymerElement)) +) + +class AdblockScriptletList extends AdblockScriptletListBase { + static get is() { + return 'adblock-scriptlet-list' + } + + static get template() { + return getTemplate() + } + + static get properties() { + return { + customScriptletsList_: { + type: Array + }, + editingScriptlet_: Scriptlet, + isEditing_: Boolean + } + } + + static get observers() { + return ['onDevModeChanged_(prefs.brave.ad_block.developer_mode.value)'] + } + + customScriptletsList_: Scriptlet[] + editingScriptlet_: Scriptlet | null = null + isEditing_: boolean = false + + browserProxy_ = BraveAdblockBrowserProxyImpl.getInstance() + + override ready() { + super.ready() + + if (loadTimeData.getBoolean('shouldExposeElementsForTesting')) { + window.testing = window.testing || {} + window.testing[`adblockScriptletList`] = this.shadowRoot + } + + this.isEditing_ = false + this.browserProxy_.getCustomScriptlets().then((scriptlets) => { + this.customScriptletsList_ = scriptlets + }) + } + + private onDevModeChanged_(value: boolean) { + if (!value) { + this.customScriptletsList_ = [] + } else { + setTimeout(() => { + this.browserProxy_.getCustomScriptlets().then((scriptlets) => { + this.customScriptletsList_ = scriptlets + }) + }) + } + } + + handleAdd_(_: any) { + this.editingScriptlet_ = new Scriptlet() + this.isEditing_ = true + } + + handleEdit_(e: any) { + this.editingScriptlet_ = this.customScriptletsList_[e.model.index] + this.isEditing_ = true + } + + handleDelete_(e: any) { + if (!loadTimeData.getBoolean('shouldExposeElementsForTesting')) { + const messageText = this.i18n('adblockCustomScriptletDeleteConfirmation') + if (!confirm(messageText)) { + return + } + } + + this.browserProxy_.removeCustomScriptlet( + this.customScriptletsList_[e.model.index].name + ) + this.browserProxy_.getCustomScriptlets().then((scriptlets) => { + this.customScriptletsList_ = scriptlets + }) + } + + scriptletEditorClosed_(_: any) { + this.editingScriptlet_ = null + this.isEditing_ = false + this.browserProxy_.getCustomScriptlets().then((scriptlets) => { + this.customScriptletsList_ = scriptlets + }) + } +} + +customElements.define(AdblockScriptletList.is, AdblockScriptletList) diff --git a/browser/resources/settings/default_brave_shields_page/default_brave_shields_page.html b/browser/resources/settings/default_brave_shields_page/default_brave_shields_page.html index 82f02dbc5dd3..eb767bbb4df2 100644 --- a/browser/resources/settings/default_brave_shields_page/default_brave_shields_page.html +++ b/browser/resources/settings/default_brave_shields_page/default_brave_shields_page.html @@ -135,7 +135,7 @@ - + diff --git a/browser/resources/settings/sources.gni b/browser/resources/settings/sources.gni index 41f3df19e0df..bab0ef51bd3f 100644 --- a/browser/resources/settings/sources.gni +++ b/browser/resources/settings/sources.gni @@ -52,6 +52,8 @@ brave_settings_web_component_files = [ "brave_web3_domains_page/brave_web3_domains_page.ts", "default_brave_shields_page/brave_adblock_subpage.ts", "default_brave_shields_page/components/brave_adblock_editor.ts", + "default_brave_shields_page/components/brave_adblock_scriptlet_editor.ts", + "default_brave_shields_page/components/brave_adblock_scriptlet_list.ts", "default_brave_shields_page/components/brave_adblock_subscribe_dropdown.ts", "default_brave_shields_page/default_brave_shields_page.ts", "getting_started_page/getting_started.ts", diff --git a/browser/ui/webui/brave_adblock_ui.cc b/browser/ui/webui/brave_adblock_ui.cc index 82b8021e253b..a40cca776d37 100644 --- a/browser/ui/webui/brave_adblock_ui.cc +++ b/browser/ui/webui/brave_adblock_ui.cc @@ -177,13 +177,14 @@ void AdblockDOMHandler::HandleGetListSubscriptions( void AdblockDOMHandler::HandleUpdateCustomFilters( const base::Value::List& args) { DCHECK_EQ(args.size(), 1U); - if (!args[0].is_string()) + if (!args[0].is_string()) { return; + } std::string custom_filters = args[0].GetString(); g_brave_browser_process->ad_block_service() ->custom_filters_provider() - ->UpdateCustomFilters(custom_filters); + ->UpdateCustomFiltersFromSettings(custom_filters); } void AdblockDOMHandler::HandleSubmitNewSubscription( diff --git a/browser/ui/webui/settings/brave_adblock_handler.cc b/browser/ui/webui/settings/brave_adblock_handler.cc index c2fc7523178a..faca5c1d8443 100644 --- a/browser/ui/webui/settings/brave_adblock_handler.cc +++ b/browser/ui/webui/settings/brave_adblock_handler.cc @@ -9,6 +9,7 @@ #include #include +#include "base/feature_list.h" #include "base/functional/bind.h" #include "base/json/values_util.h" #include "base/values.h" @@ -17,6 +18,8 @@ #include "brave/components/brave_shields/content/browser/ad_block_custom_filters_provider.h" #include "brave/components/brave_shields/content/browser/ad_block_service.h" #include "brave/components/brave_shields/core/browser/ad_block_component_service_manager.h" +#include "brave/components/brave_shields/core/browser/ad_block_custom_resource_provider.h" +#include "brave/components/brave_shields/core/common/features.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/profiles/profile.h" @@ -86,6 +89,26 @@ void BraveAdBlockHandler::RegisterMessages() { "brave_adblock.updateCustomFilters", base::BindRepeating(&BraveAdBlockHandler::UpdateCustomFilters, base::Unretained(this))); + + web_ui()->RegisterMessageCallback( + "brave_adblock.getCustomScriptlets", + base::BindRepeating(&BraveAdBlockHandler::GetCustomScriptlets, + base::Unretained(this))); + + web_ui()->RegisterMessageCallback( + "brave_adblock.addCustomScriptlet", + base::BindRepeating(&BraveAdBlockHandler::AddCustomScriptlet, + base::Unretained(this))); + + web_ui()->RegisterMessageCallback( + "brave_adblock.updateCustomScriptlet", + base::BindRepeating(&BraveAdBlockHandler::UpdateCustomScriptlet, + base::Unretained(this))); + + web_ui()->RegisterMessageCallback( + "brave_adblock.removeCustomScriptlet", + base::BindRepeating(&BraveAdBlockHandler::RemoveCustomScriptlet, + base::Unretained(this))); } void BraveAdBlockHandler::OnJavascriptAllowed() { @@ -117,8 +140,9 @@ void BraveAdBlockHandler::GetRegionalLists(const base::Value::List& args) { void BraveAdBlockHandler::EnableFilterList(const base::Value::List& args) { DCHECK_EQ(args.size(), 2U); - if (!args[0].is_string() || !args[1].is_bool()) + if (!args[0].is_string() || !args[1].is_bool()) { return; + } std::string uuid = args[0].GetString(); bool enabled = args[1].GetBool(); @@ -162,14 +186,16 @@ void BraveAdBlockHandler::GetCustomFilters(const base::Value::List& args) { void BraveAdBlockHandler::AddSubscription(const base::Value::List& args) { DCHECK_EQ(args.size(), 1U); AllowJavascript(); - if (!args[0].is_string()) + if (!args[0].is_string()) { return; + } std::string subscription_url_string = args[0].GetString(); const GURL subscription_url = GURL(subscription_url_string); - if (!subscription_url.is_valid()) + if (!subscription_url.is_valid()) { return; + } g_brave_browser_process->ad_block_service() ->subscription_service_manager() @@ -182,14 +208,16 @@ void BraveAdBlockHandler::SetSubscriptionEnabled( const base::Value::List& args) { DCHECK_EQ(args.size(), 2U); AllowJavascript(); - if (!args[0].is_string() || !args[1].is_bool()) + if (!args[0].is_string() || !args[1].is_bool()) { return; + } std::string subscription_url_string = args[0].GetString(); bool enabled = args[1].GetBool(); const GURL subscription_url = GURL(subscription_url_string); - if (!subscription_url.is_valid()) + if (!subscription_url.is_valid()) { return; + } g_brave_browser_process->ad_block_service() ->subscription_service_manager() ->EnableSubscription(subscription_url, enabled); @@ -200,8 +228,9 @@ void BraveAdBlockHandler::SetSubscriptionEnabled( void BraveAdBlockHandler::UpdateSubscription(const base::Value::List& args) { DCHECK_EQ(args.size(), 1U); AllowJavascript(); - if (!args[0].is_string()) + if (!args[0].is_string()) { return; + } std::string subscription_url_string = args[0].GetString(); const GURL subscription_url = GURL(subscription_url_string); @@ -217,8 +246,9 @@ void BraveAdBlockHandler::UpdateSubscription(const base::Value::List& args) { void BraveAdBlockHandler::DeleteSubscription(const base::Value::List& args) { DCHECK_EQ(args.size(), 1U); AllowJavascript(); - if (!args[0].is_string()) + if (!args[0].is_string()) { return; + } std::string subscription_url_string = args[0].GetString(); const GURL subscription_url = GURL(subscription_url_string); @@ -235,8 +265,9 @@ void BraveAdBlockHandler::DeleteSubscription(const base::Value::List& args) { void BraveAdBlockHandler::ViewSubscriptionSource( const base::Value::List& args) { DCHECK_EQ(args.size(), 1U); - if (!args[0].is_string()) + if (!args[0].is_string()) { return; + } std::string subscription_url_string = args[0].GetString(); const GURL subscription_url = GURL(subscription_url_string); @@ -253,8 +284,9 @@ void BraveAdBlockHandler::ViewSubscriptionSource( } void BraveAdBlockHandler::UpdateCustomFilters(const base::Value::List& args) { - if (!args[0].is_string()) + if (!args[0].is_string()) { return; + } std::string custom_filters = args[0].GetString(); g_brave_browser_process->ad_block_service() @@ -262,6 +294,72 @@ void BraveAdBlockHandler::UpdateCustomFilters(const base::Value::List& args) { ->UpdateCustomFilters(custom_filters); } +void BraveAdBlockHandler::GetCustomScriptlets(const base::Value::List& args) { + CHECK(base::FeatureList::IsEnabled( + brave_shields::features::kCosmeticFilteringCustomScriptlets)); + CHECK(args.size() == 1u && args[0].is_string()); + + g_brave_browser_process->ad_block_service() + ->custom_resource_provider() + ->GetCustomResources( + base::BindOnce(&BraveAdBlockHandler::OnGetCustomScriptlets, + weak_factory_.GetWeakPtr(), args[0].GetString())); +} + +void BraveAdBlockHandler::OnGetCustomScriptlets(const std::string& callback_id, + base::Value custom_resources) { + AllowJavascript(); + ResolveJavascriptCallback(callback_id, custom_resources); +} + +void BraveAdBlockHandler::AddCustomScriptlet(const base::Value::List& args) { + CHECK(base::FeatureList::IsEnabled( + brave_shields::features::kCosmeticFilteringCustomScriptlets)); + CHECK(args.size() == 2u && args[0].is_string() && args[1].is_dict()); + + g_brave_browser_process->ad_block_service() + ->custom_resource_provider() + ->AddResource( + args[1], + base::BindOnce(&BraveAdBlockHandler::OnScriptletUpdateStatus, + weak_factory_.GetWeakPtr(), args[0].GetString())); +} + +void BraveAdBlockHandler::UpdateCustomScriptlet(const base::Value::List& args) { + CHECK(base::FeatureList::IsEnabled( + brave_shields::features::kCosmeticFilteringCustomScriptlets)); + CHECK(args.size() == 3u && args[0].is_string() && args[1].is_string() && + args[2].is_dict()); + + g_brave_browser_process->ad_block_service() + ->custom_resource_provider() + ->UpdateResource( + args[1].GetString(), args[2], + base::BindOnce(&BraveAdBlockHandler::OnScriptletUpdateStatus, + weak_factory_.GetWeakPtr(), args[0].GetString())); +} + +void BraveAdBlockHandler::RemoveCustomScriptlet(const base::Value::List& args) { + CHECK(base::FeatureList::IsEnabled( + brave_shields::features::kCosmeticFilteringCustomScriptlets)); + CHECK(args.size() == 2u && args[0].is_string() && args[1].is_string()); + + g_brave_browser_process->ad_block_service() + ->custom_resource_provider() + ->RemoveResource( + args[1].GetString(), + base::BindOnce(&BraveAdBlockHandler::OnScriptletUpdateStatus, + weak_factory_.GetWeakPtr(), args[0].GetString())); +} + +void BraveAdBlockHandler::OnScriptletUpdateStatus( + const std::string& callback_id, + brave_shields::AdBlockCustomResourceProvider::ErrorCode error_code) { + AllowJavascript(); + ResolveJavascriptCallback(callback_id, + base::Value(static_cast(error_code))); +} + void BraveAdBlockHandler::RefreshSubscriptionsList() { FireWebUIListener("brave_adblock.onGetListSubscriptions", GetSubscriptions()); } diff --git a/browser/ui/webui/settings/brave_adblock_handler.h b/browser/ui/webui/settings/brave_adblock_handler.h index d4c84ec4deb2..90656c8ca324 100644 --- a/browser/ui/webui/settings/brave_adblock_handler.h +++ b/browser/ui/webui/settings/brave_adblock_handler.h @@ -8,12 +8,12 @@ #include -#include "base/scoped_observation.h" - #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" +#include "base/scoped_observation.h" #include "brave/components/brave_shields/content/browser/ad_block_subscription_service_manager.h" #include "brave/components/brave_shields/content/browser/ad_block_subscription_service_manager_observer.h" +#include "brave/components/brave_shields/core/browser/ad_block_custom_resource_provider.h" #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" class Profile; @@ -49,6 +49,15 @@ class BraveAdBlockHandler : public settings::SettingsPageUIHandler, void DeleteSubscription(const base::Value::List& args); void ViewSubscriptionSource(const base::Value::List& args); void UpdateCustomFilters(const base::Value::List& args); + void GetCustomScriptlets(const base::Value::List& args); + void OnGetCustomScriptlets(const std::string& callback_id, + base::Value custom_resources); + void AddCustomScriptlet(const base::Value::List& args); + void UpdateCustomScriptlet(const base::Value::List& args); + void RemoveCustomScriptlet(const base::Value::List& args); + void OnScriptletUpdateStatus( + const std::string& callback_id, + brave_shields::AdBlockCustomResourceProvider::ErrorCode error_code); void RefreshSubscriptionsList(); diff --git a/browser/ui/webui/settings/brave_settings_localized_strings_provider.cc b/browser/ui/webui/settings/brave_settings_localized_strings_provider.cc index 8c56ac0c6952..1897ad99cf96 100644 --- a/browser/ui/webui/settings/brave_settings_localized_strings_provider.cc +++ b/browser/ui/webui/settings/brave_settings_localized_strings_provider.cc @@ -847,6 +847,34 @@ void BraveAddCommonStrings(content::WebUIDataSource* html_source, {"adblockSubscribeUrlUpdateFailed", IDS_BRAVE_ADBLOCK_SUBSCRIBE_URL_UPDATE_FAILED}, {"adblockCustomListsLabel", IDS_BRAVE_ADBLOCK_CUSTOM_LISTS_LABEL}, + {"adblockDeveloperModeLabel", IDS_BRAVE_ADBLOCK_DEVELOPER_MODE_LABEL}, + {"adblockDeveloperModeDesc", IDS_BRAVE_ADBLOCK_DEVELOPER_MODE_DESC}, + {"adblockCustomSciptletsListLabel", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLETS_LIST_LABEL}, + {"adblockAddCustomScriptletButton", + IDS_BRAVE_ADBLOCK_ADD_CUSTOM_SCRIPTLET_BUTTON}, + {"adblockAddCustomScriptletDialogTitle", + IDS_BRAVE_ADBLOCK_ADD_CUSTOM_SCRIPTLET_DIALOG_TITLE}, + {"adblockEditCustomScriptletDialogTitle", + IDS_BRAVE_ADBLOCK_EDIT_CUSTOM_SCRIPTLET_DIALOG_TITLE}, + {"adblockCustomSciptletDialogNameLabel", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DIALOG_NAME_LABEL}, + {"adblockCustomScriptletDialogContentLabel", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DIALOG_CONTENT_LABEL}, + {"adblockCustomScriptletDialogCancelButton", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DIALOG_CANCEL_BUTTON}, + {"adblockCustomScriptletDialogSaveButton", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DIALOG_SAVE_BUTTON}, + {"adblockCustomScriptletDeleteConfirmation", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DELETE_CONFIRMATION}, + {"adblockCustomScriptletAlreadyExistsError", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_ALREADY_EXISTS_ERROR}, + {"adblockCustomScriptletInvalidNameError", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_INVALID_NAME_ERROR}, + {"adblockCustomScriptletNotFoundError", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_NOT_FOUND_ERROR}, + {"adblockCustomScriptletWarning", + IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_WARNING}, {"braveShortcutsPage", IDS_SETTINGS_BRAVE_SHORTCUTS_TITLE}, {"shortcutsPageSearchPlaceholder", IDS_SHORTCUTS_PAGE_SEARCH_PLACEHOLDER}, @@ -1005,6 +1033,11 @@ void BraveAddLocalizedStrings(content::WebUIDataSource* html_source, html_source->AddLocalizedStrings(kSessionOnlyToEphemeralStrings); } + html_source->AddBoolean( + "cosmeticFilteringCustomScriptletsEnabled", + base::FeatureList::IsEnabled( + brave_shields::features::kCosmeticFilteringCustomScriptlets)); + // Always disable upstream's side panel align option. // We add our customized option at preferred position. html_source->AddBoolean("showSidePanelOptions", false); diff --git a/chromium_src/base/trace_event/memory_infra_background_allowlist.cc b/chromium_src/base/trace_event/memory_infra_background_allowlist.cc index 8e78022b602c..7f7fef50020a 100644 --- a/chromium_src/base/trace_event/memory_infra_background_allowlist.cc +++ b/chromium_src/base/trace_event/memory_infra_background_allowlist.cc @@ -17,6 +17,10 @@ bool IsMemoryAllocatorDumpNameInAllowlist(const std::string& name) { "extensions/value_store/Extensions.Database.Open.BraveWallet/")) { return true; } + if (name.starts_with("extensions/value_store/" + "Extensions.Database.Open.AdBlock Custom Resources/")) { + return true; + } return IsMemoryAllocatorDumpNameInAllowlist_ChromiumImpl(name); } diff --git a/chromium_src/chrome/browser/prefs/chrome_pref_service_factory.cc b/chromium_src/chrome/browser/prefs/chrome_pref_service_factory.cc new file mode 100644 index 000000000000..45a097f3dc92 --- /dev/null +++ b/chromium_src/chrome/browser/prefs/chrome_pref_service_factory.cc @@ -0,0 +1,17 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "chrome/browser/prefs/chrome_pref_service_factory.h" + +#include "brave/components/brave_shields/core/common/pref_names.h" + +#define BRAVE_TRACKED_PREFS_EXTEND \ + {1000, brave_shields::prefs::kAdBlockDeveloperMode, \ + EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::ATOMIC, \ + ValueType::IMPERSONAL}, + +#include "src/chrome/browser/prefs/chrome_pref_service_factory.cc" + +#undef BRAVE_TRACKED_PREFS_EXTEND diff --git a/chromium_src/chrome/browser/prefs/sources.gni b/chromium_src/chrome/browser/prefs/sources.gni index 7d5814edf9b0..81d84ce0a9ea 100644 --- a/chromium_src/chrome/browser/prefs/sources.gni +++ b/chromium_src/chrome/browser/prefs/sources.gni @@ -6,6 +6,7 @@ import("//third_party/widevine/cdm/widevine.gni") brave_chromium_src_chrome_browser_prefs_deps = [ + "//brave/components/brave_shields/core/common", "//brave/components/brave_sync", "//brave/components/brave_vpn/common/buildflags", "//brave/components/brave_wallet/browser", diff --git a/components/brave_shields/content/browser/ad_block_custom_filters_provider.cc b/components/brave_shields/content/browser/ad_block_custom_filters_provider.cc index 1af34f41efed..0d1359d5ba9e 100644 --- a/components/brave_shields/content/browser/ad_block_custom_filters_provider.cc +++ b/components/brave_shields/content/browser/ad_block_custom_filters_provider.cc @@ -37,6 +37,14 @@ AdBlockCustomFiltersProvider::AdBlockCustomFiltersProvider( AdBlockCustomFiltersProvider::~AdBlockCustomFiltersProvider() {} +void AdBlockCustomFiltersProvider::EnableDeveloperMode(bool enabled) { + if (developer_mode_enabled_ == enabled) { + return; + } + developer_mode_enabled_ = enabled; + NotifyObservers(engine_is_default_); +} + void AdBlockCustomFiltersProvider::AddUserCosmeticFilter( const std::string& filter) { std::string custom_filters = GetCustomFilters(); @@ -75,6 +83,14 @@ bool AdBlockCustomFiltersProvider::UpdateCustomFilters( return true; } +bool AdBlockCustomFiltersProvider::UpdateCustomFiltersFromSettings( + const std::string& custom_filters) { + if (!developer_mode_enabled_) { + return false; + } + return UpdateCustomFilters(custom_filters); +} + void AdBlockCustomFiltersProvider::LoadFilterSet( base::OnceCallback< void(base::OnceCallback*)>)> cb) { diff --git a/components/brave_shields/content/browser/ad_block_custom_filters_provider.h b/components/brave_shields/content/browser/ad_block_custom_filters_provider.h index a15d7aa5fc94..8fc7366136a2 100644 --- a/components/brave_shields/content/browser/ad_block_custom_filters_provider.h +++ b/components/brave_shields/content/browser/ad_block_custom_filters_provider.h @@ -28,12 +28,18 @@ class AdBlockCustomFiltersProvider : public AdBlockFiltersProvider { AdBlockCustomFiltersProvider& operator=(const AdBlockCustomFiltersProvider&) = delete; + void EnableDeveloperMode(bool enabled); + void AddUserCosmeticFilter(const std::string& filter); void CreateSiteExemption(const std::string& host); std::string GetCustomFilters(); bool UpdateCustomFilters(const std::string& custom_filters); + // Used in BraveAdBlockHandler and updates the manually edited custom filters + // only if developer mode is turned on. + bool UpdateCustomFiltersFromSettings(const std::string& custom_filters); + // AdBlockFiltersProvider void LoadFilterSet( base::OnceCallback local_state_; + bool developer_mode_enabled_ = false; SEQUENCE_CHECKER(sequence_checker_); }; diff --git a/components/brave_shields/content/browser/ad_block_pref_service.cc b/components/brave_shields/content/browser/ad_block_pref_service.cc index 79a4d74a283e..97b812fe6c78 100644 --- a/components/brave_shields/content/browser/ad_block_pref_service.cc +++ b/components/brave_shields/content/browser/ad_block_pref_service.cc @@ -35,7 +35,8 @@ std::string GetTagFromPrefName(const std::string& pref_name) { } // namespace AdBlockPrefService::AdBlockPrefService(AdBlockService* ad_block_service, - PrefService* prefs) + PrefService* prefs, + PrefService* local_state) : ad_block_service_(ad_block_service), prefs_(prefs) { pref_change_registrar_.reset(new PrefChangeRegistrar()); pref_change_registrar_->Init(prefs_); @@ -56,6 +57,12 @@ AdBlockPrefService::AdBlockPrefService(AdBlockService* ad_block_service, OnPreferenceChanged(prefs::kFBEmbedControlType); OnPreferenceChanged(prefs::kTwitterEmbedControlType); OnPreferenceChanged(prefs::kLinkedInEmbedControlType); + + pref_change_registrar_->Add( + prefs::kAdBlockDeveloperMode, + base::BindRepeating(&AdBlockPrefService::OnDeveloperModeChanged, + base::Unretained(this))); + OnDeveloperModeChanged(); } AdBlockPrefService::~AdBlockPrefService() = default; @@ -110,6 +117,11 @@ void AdBlockPrefService::OnPreferenceChanged(const std::string& pref_name) { ad_block_service_->EnableTag(tag, enabled); } +void AdBlockPrefService::OnDeveloperModeChanged() { + const bool enabled = prefs_->GetBoolean(prefs::kAdBlockDeveloperMode); + ad_block_service_->EnableDeveloperMode(enabled); +} + void AdBlockPrefService::OnProxyConfigChanged( const net::ProxyConfigWithAnnotation& config, net::ProxyConfigService::ConfigAvailability availability) { diff --git a/components/brave_shields/content/browser/ad_block_pref_service.h b/components/brave_shields/content/browser/ad_block_pref_service.h index c747237a65e2..6b141a1c2f10 100644 --- a/components/brave_shields/content/browser/ad_block_pref_service.h +++ b/components/brave_shields/content/browser/ad_block_pref_service.h @@ -27,7 +27,8 @@ class AdBlockPrefService : public KeyedService, public net::ProxyConfigService::Observer { public: explicit AdBlockPrefService(AdBlockService* ad_block_service, - PrefService* prefs); + PrefService* prefs, + PrefService* local_state); ~AdBlockPrefService() override; void StartProxyTracker( @@ -40,6 +41,7 @@ class AdBlockPrefService : public KeyedService, void Shutdown() override; void OnPreferenceChanged(const std::string& pref_name); + void OnDeveloperModeChanged(); // net::ProxyConfigService::Observer: void OnProxyConfigChanged( diff --git a/components/brave_shields/content/browser/ad_block_service.cc b/components/brave_shields/content/browser/ad_block_service.cc index a823cf99b892..7e36da10927e 100644 --- a/components/brave_shields/content/browser/ad_block_service.cc +++ b/components/brave_shields/content/browser/ad_block_service.cc @@ -22,6 +22,7 @@ #include "brave/components/brave_shields/content/browser/ad_block_subscription_service_manager.h" #include "brave/components/brave_shields/core/browser/ad_block_component_filters_provider.h" #include "brave/components/brave_shields/core/browser/ad_block_component_service_manager.h" +#include "brave/components/brave_shields/core/browser/ad_block_custom_resource_provider.h" #include "brave/components/brave_shields/core/browser/ad_block_default_resource_provider.h" #include "brave/components/brave_shields/core/browser/ad_block_filter_list_catalog_provider.h" #include "brave/components/brave_shields/core/browser/ad_block_filters_provider_manager.h" @@ -264,6 +265,11 @@ AdBlockCustomFiltersProvider* AdBlockService::custom_filters_provider() { return custom_filters_provider_.get(); } +AdBlockCustomResourceProvider* AdBlockService::custom_resource_provider() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return custom_resource_provider_.get(); +} + AdBlockSubscriptionServiceManager* AdBlockService::subscription_service_manager() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -306,8 +312,19 @@ AdBlockService::AdBlockService( SetupDiscardPolicy(policy); } - resource_provider_ = std::make_unique( - component_update_service_); + auto default_resource_provider = + std::make_unique( + component_update_service_); + default_resource_provider_ = default_resource_provider.get(); + + if (base::FeatureList::IsEnabled( + features::kCosmeticFilteringCustomScriptlets)) { + custom_resource_provider_ = new AdBlockCustomResourceProvider( + profile_dir_, std::move(default_resource_provider)); + resource_provider_.reset(custom_resource_provider_.get()); + } else { + resource_provider_ = std::move(default_resource_provider); + } filter_list_catalog_provider_ = std::make_unique( component_update_service_); @@ -341,6 +358,15 @@ AdBlockService::AdBlockService( AdBlockService::~AdBlockService() = default; +void AdBlockService::EnableDeveloperMode(bool enabled) { + if (custom_resource_provider()) { + custom_resource_provider()->EnableDeveloperMode(enabled); + } + if (custom_filters_provider()) { + custom_filters_provider()->EnableDeveloperMode(enabled); + } +} + void AdBlockService::EnableTag(const std::string& tag, bool enabled) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Tags only need to be modified for the default engine. @@ -407,9 +433,9 @@ void RegisterPrefsForAdBlockService(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(prefs::kAdBlockCheckedAllDefaultRegions, false); } -AdBlockResourceProvider* AdBlockService::resource_provider() { +AdBlockDefaultResourceProvider* AdBlockService::default_resource_provider() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return resource_provider_.get(); + return default_resource_provider_.get(); } void AdBlockService::UseSourceProviderForTest( diff --git a/components/brave_shields/content/browser/ad_block_service.h b/components/brave_shields/content/browser/ad_block_service.h index f7966d2011ff..801eea0d2117 100644 --- a/components/brave_shields/content/browser/ad_block_service.h +++ b/components/brave_shields/content/browser/ad_block_service.h @@ -47,6 +47,7 @@ class AdBlockComponentFiltersProvider; class AdBlockDefaultResourceProvider; class AdBlockComponentServiceManager; class AdBlockCustomFiltersProvider; +class AdBlockCustomResourceProvider; class AdBlockLocalhostFiltersProvider; class AdBlockFilterListCatalogProvider; class AdBlockSubscriptionServiceManager; @@ -81,9 +82,11 @@ class AdBlockService { void OnResourcesLoaded(const std::string& resources_json) override; std::unique_ptr> filter_set_; - raw_ptr adblock_engine_; - raw_ptr filters_provider_; // not owned - raw_ptr resource_provider_; // not owned + raw_ptr adblock_engine_ = nullptr; // not owned + raw_ptr filters_provider_ = nullptr; // not owned + raw_ptr resource_provider_ = nullptr; // not owned + raw_ptr custom_resource_provider_ = + nullptr; // not owned scoped_refptr task_runner_; bool is_filter_provider_manager_; @@ -123,7 +126,9 @@ class AdBlockService { AdBlockComponentServiceManager* component_service_manager(); AdBlockSubscriptionServiceManager* subscription_service_manager(); AdBlockCustomFiltersProvider* custom_filters_provider(); + AdBlockCustomResourceProvider* custom_resource_provider(); + void EnableDeveloperMode(bool enabled); void EnableTag(const std::string& tag, bool enabled); void AddUserCosmeticFilter(const std::string& filter); @@ -149,7 +154,7 @@ class AdBlockService { static std::string g_ad_block_dat_file_version_; - AdBlockResourceProvider* resource_provider(); + AdBlockDefaultResourceProvider* default_resource_provider(); AdBlockComponentFiltersProvider* default_filters_provider() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return default_filters_provider_.get(); @@ -181,8 +186,12 @@ class AdBlockService { AdBlockListP3A list_p3a_; - std::unique_ptr resource_provider_ + std::unique_ptr resource_provider_ GUARDED_BY_CONTEXT(sequence_checker_); + raw_ptr default_resource_provider_ + GUARDED_BY_CONTEXT(sequence_checker_) = nullptr; + raw_ptr custom_resource_provider_ + GUARDED_BY_CONTEXT(sequence_checker_) = nullptr; std::unique_ptr custom_filters_provider_ GUARDED_BY_CONTEXT(sequence_checker_); std::unique_ptr localhost_filters_provider_ diff --git a/components/brave_shields/core/browser/BUILD.gn b/components/brave_shields/core/browser/BUILD.gn index efde19effef2..1e9ff7e9949f 100644 --- a/components/brave_shields/core/browser/BUILD.gn +++ b/components/brave_shields/core/browser/BUILD.gn @@ -11,6 +11,8 @@ static_library("browser") { "ad_block_component_installer.h", "ad_block_component_service_manager.cc", "ad_block_component_service_manager.h", + "ad_block_custom_resource_provider.cc", + "ad_block_custom_resource_provider.h", "ad_block_default_resource_provider.cc", "ad_block_default_resource_provider.h", "ad_block_filter_list_catalog_provider.cc", @@ -35,6 +37,7 @@ static_library("browser") { "//brave/components/brave_shields/core/common", "//components/component_updater:component_updater", "//components/prefs", + "//components/value_store", "//crypto", ] public_deps = [ "//base" ] diff --git a/components/brave_shields/core/browser/ad_block_custom_resource_provider.cc b/components/brave_shields/core/browser/ad_block_custom_resource_provider.cc new file mode 100644 index 000000000000..add0cdc63227 --- /dev/null +++ b/components/brave_shields/core/browser/ad_block_custom_resource_provider.cc @@ -0,0 +1,298 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "brave/components/brave_shields/core/browser/ad_block_custom_resource_provider.h" + +#include +#include +#include + +#include "base/feature_list.h" +#include "base/json/json_writer.h" +#include "base/ranges/algorithm.h" +#include "base/strings/strcat.h" +#include "base/strings/string_util.h" +#include "base/task/sequenced_task_runner.h" +#include "brave/components/brave_shields/core/common/features.h" +#include "components/value_store/value_store_factory_impl.h" +#include "components/value_store/value_store_frontend.h" +#include "components/value_store/value_store_task_runner.h" + +namespace brave_shields { + +namespace { +constexpr const char kStorageUMA[] = "AdBlock Custom Resources"; +constexpr const base::FilePath::CharType kStorageName[] = + FILE_PATH_LITERAL("AdBlock Custom Resources"); +constexpr const char kStorageScriptletsKey[] = "SCRIPTLETS"; + +constexpr const char kNameField[] = "name"; +constexpr const char kContentField[] = "content"; +constexpr const char kMimeField[] = "kind.mime"; +constexpr const char kAppJs[] = "application/javascript"; + +bool HasName(const base::Value& resource) { + return resource.is_dict() && !!resource.GetDict().FindString(kNameField); +} + +bool IsValidResource(const base::Value& resource) { + if (!HasName(resource)) { + return false; + } + if (!resource.GetDict().FindString(kContentField)) { + // Invalid resource structure. + return false; + } + + const auto* name = resource.GetDict().FindString(kNameField); + if (name->empty() || !base::IsStringASCII(*name)) { + return false; + } + + const auto* mime = resource.GetDict().FindStringByDottedPath(kMimeField); + if (!mime) { + return false; + } + + if (*mime == kAppJs) { + // Resource is a scriptlet: + if (!name->starts_with("user-") || !name->ends_with(".js")) { + return false; + } + } else { + return false; + } + + return true; +} + +const std::string& GetResourceName(const base::Value& resource) { + if (!HasName(resource)) { + return base::EmptyString(); + } + return *resource.GetDict().FindString(kNameField); +} + +base::Value::List::iterator FindResource(base::Value::List& resources, + const std::string& name) { + return base::ranges::find_if(resources, [name](const base::Value& v) { + return GetResourceName(v) == name; + }); +} + +std::string_view JsonListStr(std::string_view json) { + const auto start = json.find('['); + const auto end = json.rfind(']'); + if (start == std::string_view::npos || end == std::string_view::npos || + start >= end) { + return std::string_view(); + } + return json.substr(start + 1, end - start - 1); +} + +std::string MergeResources(const std::string& default_resources, + const std::string& custom_resources) { + auto default_resources_str = JsonListStr(default_resources); + if (default_resources_str.empty()) { + return custom_resources; + } + auto custom_resources_str = JsonListStr(custom_resources); + if (custom_resources_str.empty()) { + return default_resources; + } + return base::StrCat( + {"[", default_resources_str, ",", custom_resources_str, "]"}); +} + +} // namespace + +AdBlockCustomResourceProvider::AdBlockCustomResourceProvider( + const base::FilePath& storage_root, + std::unique_ptr default_resource_provider) + : default_resource_provider_(std::move(default_resource_provider)) { + CHECK(base::FeatureList::IsEnabled( + brave_shields::features::kCosmeticFilteringCustomScriptlets)); + CHECK(default_resource_provider_); + auto factory = + base::MakeRefCounted(storage_root); + storage_ = std::make_unique( + std::move(factory), base::FilePath(kStorageName), kStorageUMA, + base::SequencedTaskRunner::GetCurrentDefault(), + value_store::GetValueStoreTaskRunner()); + default_resource_provider_->AddObserver(this); +} + +AdBlockCustomResourceProvider::~AdBlockCustomResourceProvider() { + default_resource_provider_->RemoveObserver(this); +} + +void AdBlockCustomResourceProvider::EnableDeveloperMode(bool enabled) { + if (developer_mode_enabled_ == enabled) { + return; + } + developer_mode_enabled_ = enabled; + ReloadResourcesAndNotify(); +} + +void AdBlockCustomResourceProvider::GetCustomResources( + base::OnceCallback callback) { + if (!developer_mode_enabled_) { + return std::move(callback).Run(base::Value(base::Value::Type::LIST)); + } + + storage_->Get( + kStorageScriptletsKey, + base::BindOnce( + [](base::OnceCallback callback, + std::optional value) { + if (value && value->is_list()) { + std::move(callback).Run(std::move(*value)); + } else { + std::move(callback).Run(base::Value(base::Value::Type::LIST)); + } + }, + std::move(callback))); +} + +void AdBlockCustomResourceProvider::AddResource(const base::Value& resource, + StatusCallback on_complete) { + if (!IsValidResource(resource)) { + return std::move(on_complete).Run(ErrorCode::kInvalid); + } + GetCustomResources( + base::BindOnce(&AdBlockCustomResourceProvider::AddResourceInternal, + weak_ptr_factory_.GetWeakPtr(), resource.Clone(), + std::move(on_complete))); +} + +void AdBlockCustomResourceProvider::UpdateResource(const std::string& old_name, + const base::Value& resource, + StatusCallback on_complete) { + if (!IsValidResource(resource)) { + return std::move(on_complete).Run(ErrorCode::kInvalid); + } + GetCustomResources( + base::BindOnce(&AdBlockCustomResourceProvider::UpdateResourceInternal, + weak_ptr_factory_.GetWeakPtr(), old_name, resource.Clone(), + std::move(on_complete))); +} + +void AdBlockCustomResourceProvider::RemoveResource( + const std::string& resource_name, + StatusCallback on_complete) { + GetCustomResources(base::BindOnce( + &AdBlockCustomResourceProvider::RemoveResourceInternal, + weak_ptr_factory_.GetWeakPtr(), resource_name, std::move(on_complete))); +} + +void AdBlockCustomResourceProvider::LoadResources( + base::OnceCallback on_load) { + default_resource_provider_->LoadResources( + base::BindOnce(&AdBlockCustomResourceProvider::OnDefaultResourcesLoaded, + weak_ptr_factory_.GetWeakPtr(), std::move(on_load))); +} + +void AdBlockCustomResourceProvider::OnResourcesLoaded( + const std::string& resources_json) { + OnDefaultResourcesLoaded( + base::BindOnce(&AdBlockCustomResourceProvider::NotifyResourcesLoaded, + weak_ptr_factory_.GetWeakPtr()), + resources_json); +} + +void AdBlockCustomResourceProvider::AddResourceInternal( + base::Value resource, + StatusCallback on_complete, + base::Value resources) { + CHECK(resources.is_list()); + auto& list = resources.GetList(); + if (FindResource(list, GetResourceName(resource)) != list.end()) { + return std::move(on_complete).Run(ErrorCode::kAlreadyExists); + } + list.Append(std::move(resource)); + SaveResources(std::move(resources)); + ReloadResourcesAndNotify(); + std::move(on_complete).Run(ErrorCode::kOk); +} + +void AdBlockCustomResourceProvider::UpdateResourceInternal( + const std::string& old_name, + base::Value resource, + StatusCallback on_complete, + base::Value resources) { + CHECK(resources.is_list()); + auto updated_resource = FindResource(resources.GetList(), old_name); + if (updated_resource == resources.GetList().end()) { + return std::move(on_complete).Run(ErrorCode::kNotFound); + } + + const std::string& new_name = GetResourceName(resource); + if (old_name != new_name) { + if (FindResource(resources.GetList(), new_name) != + resources.GetList().end()) { + return std::move(on_complete).Run(ErrorCode::kNotFound); + } + } + + *updated_resource = std::move(resource); + SaveResources(std::move(resources)); + ReloadResourcesAndNotify(); + std::move(on_complete).Run(ErrorCode::kOk); +} + +void AdBlockCustomResourceProvider::RemoveResourceInternal( + const std::string& name, + StatusCallback on_complete, + base::Value resources) { + CHECK(resources.is_list()); + auto updated_resource = FindResource(resources.GetList(), name); + if (updated_resource != resources.GetList().end()) { + resources.GetList().erase(updated_resource); + SaveResources(std::move(resources)); + ReloadResourcesAndNotify(); + std::move(on_complete).Run(ErrorCode::kOk); + } else { + std::move(on_complete).Run(ErrorCode::kNotFound); + } +} + +void AdBlockCustomResourceProvider::SaveResources(base::Value resources) { + storage_->Set(kStorageScriptletsKey, std::move(resources)); +} + +void AdBlockCustomResourceProvider::OnDefaultResourcesLoaded( + base::OnceCallback on_load, + const std::string& resources_json) { + GetCustomResources(base::BindOnce( + &AdBlockCustomResourceProvider::OnCustomResourcesLoaded, + weak_ptr_factory_.GetWeakPtr(), std::move(on_load), resources_json)); +} + +void AdBlockCustomResourceProvider::OnCustomResourcesLoaded( + base::OnceCallback on_load, + const std::string& default_resources_json, + base::Value custom_resources) { + CHECK(custom_resources.is_list()); + + if (custom_resources.GetList().empty()) { + std::move(on_load).Run(default_resources_json); + } else { + auto custom_resources_json = base::WriteJson(custom_resources); + if (!custom_resources_json) { + std::move(on_load).Run(default_resources_json); + } else { + std::move(on_load).Run( + MergeResources(default_resources_json, *custom_resources_json)); + } + } +} + +void AdBlockCustomResourceProvider::ReloadResourcesAndNotify() { + LoadResources( + base::BindOnce(&AdBlockCustomResourceProvider::NotifyResourcesLoaded, + weak_ptr_factory_.GetWeakPtr())); +} + +} // namespace brave_shields diff --git a/components/brave_shields/core/browser/ad_block_custom_resource_provider.h b/components/brave_shields/core/browser/ad_block_custom_resource_provider.h new file mode 100644 index 000000000000..9237eb0c85d2 --- /dev/null +++ b/components/brave_shields/core/browser/ad_block_custom_resource_provider.h @@ -0,0 +1,92 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_COMPONENTS_BRAVE_SHIELDS_CORE_BROWSER_AD_BLOCK_CUSTOM_RESOURCE_PROVIDER_H_ +#define BRAVE_COMPONENTS_BRAVE_SHIELDS_CORE_BROWSER_AD_BLOCK_CUSTOM_RESOURCE_PROVIDER_H_ + +#include +#include + +#include "base/files/file_path.h" +#include "base/functional/callback.h" +#include "base/values.h" +#include "brave/components/brave_shields/core/browser/ad_block_resource_provider.h" + +namespace value_store { +class ValueStoreFrontend; +} // namespace value_store + +namespace brave_shields { + +class AdBlockCustomResourceProvider + : public AdBlockResourceProvider, + private AdBlockResourceProvider::Observer { + public: + enum class ErrorCode { + kOk, + kInvalid, + kAlreadyExists, + kNotFound, + }; + + using GetCallback = base::OnceCallback; + using StatusCallback = base::OnceCallback; + + AdBlockCustomResourceProvider( + const base::FilePath& storage_root, + std::unique_ptr default_resource_provider); + ~AdBlockCustomResourceProvider() override; + + void EnableDeveloperMode(bool enabled); + + void GetCustomResources(GetCallback callback); + void AddResource(const base::Value& resource, StatusCallback on_complete); + void UpdateResource(const std::string& name, + const base::Value& resource, + StatusCallback on_complete); + void RemoveResource(const std::string& resource_name, + StatusCallback on_complete); + + // AdBlockResourceProvider: + void LoadResources( + base::OnceCallback) override; + + private: + // AdBlockResourceProvider::Observer: + void OnResourcesLoaded(const std::string& resources_json) override; + + void AddResourceInternal(base::Value resource, + StatusCallback on_complete, + base::Value resources); + void UpdateResourceInternal(const std::string& name, + base::Value resource, + StatusCallback on_complete, + base::Value resources); + void RemoveResourceInternal(const std::string& name, + StatusCallback on_complete, + base::Value resources); + + void SaveResources(base::Value resources); + + void OnDefaultResourcesLoaded( + base::OnceCallback on_load, + const std::string& resources_json); + void OnCustomResourcesLoaded( + base::OnceCallback on_load, + const std::string& default_resources, + base::Value custom_resources); + + void ReloadResourcesAndNotify(); + + std::unique_ptr default_resource_provider_ = nullptr; + std::unique_ptr storage_; + bool developer_mode_enabled_ = false; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace brave_shields + +#endif // BRAVE_COMPONENTS_BRAVE_SHIELDS_CORE_BROWSER_AD_BLOCK_CUSTOM_RESOURCE_PROVIDER_H_ diff --git a/components/brave_shields/core/browser/ad_block_default_resource_provider.cc b/components/brave_shields/core/browser/ad_block_default_resource_provider.cc index a48d48497ad0..e2e649c4418e 100644 --- a/components/brave_shields/core/browser/ad_block_default_resource_provider.cc +++ b/components/brave_shields/core/browser/ad_block_default_resource_provider.cc @@ -10,6 +10,7 @@ #include "base/files/file_path.h" #include "base/task/thread_pool.h" +#include "brave/components/brave_component_updater/browser/dat_file_util.h" #include "brave/components/brave_shields/core/browser/ad_block_component_installer.h" namespace { @@ -59,7 +60,7 @@ void AdBlockDefaultResourceProvider::OnComponentReady( FROM_HERE, {base::MayBlock()}, base::BindOnce(&brave_component_updater::GetDATFileAsString, resources_path), - base::BindOnce(&AdBlockDefaultResourceProvider::OnResourcesLoaded, + base::BindOnce(&AdBlockDefaultResourceProvider::NotifyResourcesLoaded, weak_factory_.GetWeakPtr())); } diff --git a/components/brave_shields/core/browser/ad_block_resource_provider.cc b/components/brave_shields/core/browser/ad_block_resource_provider.cc index 0f08aaee3510..7c590a5c6812 100644 --- a/components/brave_shields/core/browser/ad_block_resource_provider.cc +++ b/components/brave_shields/core/browser/ad_block_resource_provider.cc @@ -27,7 +27,7 @@ void AdBlockResourceProvider::RemoveObserver( } } -void AdBlockResourceProvider::OnResourcesLoaded( +void AdBlockResourceProvider::NotifyResourcesLoaded( const std::string& resources_json) { for (auto& observer : observers_) { observer.OnResourcesLoaded(resources_json); diff --git a/components/brave_shields/core/browser/ad_block_resource_provider.h b/components/brave_shields/core/browser/ad_block_resource_provider.h index c478ad19d238..0d5d9550ece5 100644 --- a/components/brave_shields/core/browser/ad_block_resource_provider.h +++ b/components/brave_shields/core/browser/ad_block_resource_provider.h @@ -36,7 +36,7 @@ class AdBlockResourceProvider { base::OnceCallback) = 0; protected: - void OnResourcesLoaded(const std::string& resources_json); + void NotifyResourcesLoaded(const std::string& resources_json); private: base::ObserverList observers_; diff --git a/components/brave_shields/core/common/features.cc b/components/brave_shields/core/common/features.cc index 63bd9c0219f4..0a3b8ba4949d 100644 --- a/components/brave_shields/core/common/features.cc +++ b/components/brave_shields/core/common/features.cc @@ -144,6 +144,10 @@ BASE_FEATURE(kCosmeticFilteringJsPerformance, "CosmeticFilteringJsPerformance", base::FEATURE_ENABLED_BY_DEFAULT); +BASE_FEATURE(kCosmeticFilteringCustomScriptlets, + "CosmeticFilteringCustomScriptlets", + base::FEATURE_DISABLED_BY_DEFAULT); + constexpr base::FeatureParam kComponentUpdateCheckIntervalMins{ &kAdBlockDefaultResourceUpdateInterval, "update_interval_mins", 100}; diff --git a/components/brave_shields/core/common/features.h b/components/brave_shields/core/common/features.h index cc18e324df98..5ebff99f4e56 100644 --- a/components/brave_shields/core/common/features.h +++ b/components/brave_shields/core/common/features.h @@ -40,6 +40,7 @@ BASE_DECLARE_FEATURE(kCosmeticFilteringExtraPerfMetrics); BASE_DECLARE_FEATURE(kCosmeticFilteringJsPerformance); BASE_DECLARE_FEATURE(kCosmeticFilteringSyncLoad); BASE_DECLARE_FEATURE(kBlockAllCookiesToggle); +BASE_DECLARE_FEATURE(kCosmeticFilteringCustomScriptlets); extern const base::FeatureParam kComponentUpdateCheckIntervalMins; extern const base::FeatureParam kCosmeticFilteringSubFrameFirstSelectorsPollingDelayMs; diff --git a/components/brave_shields/core/common/pref_names.h b/components/brave_shields/core/common/pref_names.h index bbe3a4a999a3..516090873ef6 100644 --- a/components/brave_shields/core/common/pref_names.h +++ b/components/brave_shields/core/common/pref_names.h @@ -25,6 +25,8 @@ inline constexpr char kAdBlockRegionalFilters[] = "brave.ad_block.regional_filters"; inline constexpr char kAdBlockListSubscriptions[] = "brave.ad_block.list_subscriptions"; +inline constexpr char kAdBlockDeveloperMode[] = "brave.ad_block.developer_mode"; + inline constexpr char kFBEmbedControlType[] = "brave.fb_embed_default"; inline constexpr char kTwitterEmbedControlType[] = "brave.twitter_embed_default"; diff --git a/patches/chrome-browser-prefs-chrome_pref_service_factory.cc.patch b/patches/chrome-browser-prefs-chrome_pref_service_factory.cc.patch new file mode 100644 index 000000000000..5f4c033cd057 --- /dev/null +++ b/patches/chrome-browser-prefs-chrome_pref_service_factory.cc.patch @@ -0,0 +1,12 @@ +diff --git a/chrome/browser/prefs/chrome_pref_service_factory.cc b/chrome/browser/prefs/chrome_pref_service_factory.cc +index 2df13b1a2d35b2f45181348845aecc4fa0bb7815..31e37ad410e58252ba07c2736be3f6e3595bb1e0 100644 +--- a/chrome/browser/prefs/chrome_pref_service_factory.cc ++++ b/chrome/browser/prefs/chrome_pref_service_factory.cc +@@ -189,6 +189,7 @@ const prefs::TrackedPreferenceMetadata kTrackedPrefs[] = { + + // See note at top, new items added here also need to be added to + // histograms.xml's TrackedPreference enum. ++ BRAVE_TRACKED_PREFS_EXTEND + }; + + // One more than the last tracked preferences ID above. diff --git a/test/BUILD.gn b/test/BUILD.gn index 67a718b37572..0ad4dbbe536d 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -1125,6 +1125,7 @@ test("brave_browser_tests") { sources += [ "//brave/browser/brave_autofill_browsertest.cc", "//brave/browser/brave_resources_browsertest.cc", + "//brave/browser/brave_shields/ad_block_custom_resources_browsertest.cc", "//brave/browser/renderer_context_menu/test/render_view_context_menu_browsertest.cc", "//brave/browser/ssl/certificate_transparency_browsertest.cc", "//brave/browser/ui/views/toolbar/wallet_button_notification_source_browsertest.cc",