Skip to content

Commit

Permalink
Added Custom Scriptlet section in the settings page. (#25999)
Browse files Browse the repository at this point in the history
* Added Custom Scriptlet section in the settings page.
* Added security warning message.
* Added adblock `Developer mode` protected pref.
* Make custom filters readonly if dev-mode is disabled.
  • Loading branch information
boocmp authored Dec 13, 2024
1 parent bc5b692 commit 1f0e2fc
Show file tree
Hide file tree
Showing 42 changed files with 1,493 additions and 37 deletions.
45 changes: 45 additions & 0 deletions app/brave_settings_strings.grdp
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,51 @@
<message name="IDS_BRAVE_ADBLOCK_FILTER_LISTS_INPUT_PLACEHOLDER" desc="A placeholder label for the input form to allow the user to filter from a list of content filters">
Filter lists
</message>
<message name="IDS_BRAVE_ADBLOCK_DEVELOPER_MODE_LABEL" desc="Label for custom scriptlets lists section">
Developer mode
</message>
<message name="IDS_BRAVE_ADBLOCK_DEVELOPER_MODE_DESC" desc="Label for custom scriptlets lists section">
Allow adding custom filters and scriptlets
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLETS_LIST_LABEL" desc="Label for custom scriptlets lists section">
Custom scriptlets
</message>
<message name="IDS_BRAVE_ADBLOCK_ADD_CUSTOM_SCRIPTLET_BUTTON" desc="A label for the add custom scriptlet button">
Add new scriptlet
</message>
<message name="IDS_BRAVE_ADBLOCK_ADD_CUSTOM_SCRIPTLET_DIALOG_TITLE" desc="A title for the add custom scriptlet dialog">
Add new scriptlet
</message>
<message name="IDS_BRAVE_ADBLOCK_EDIT_CUSTOM_SCRIPTLET_DIALOG_TITLE" desc="A title for the edit custom scriptlet dialog">
Edit scriptlet
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DIALOG_NAME_LABEL" desc="A label for the custom scriptlet name field">
Name
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DIALOG_CONTENT_LABEL" desc="A label for the custom scriptlet content field">
Content
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DIALOG_CANCEL_BUTTON" desc="A label for the custom scriptlet content cancel button">
Cancel
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DIALOG_SAVE_BUTTON" desc="A label for the custom scriptlet content save button">
Save
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_DELETE_CONFIRMATION" desc="A confirmation message for the custom scriptlet deletion">
Are you sure you want to delete this scriptlet? If this scriptlet is used in any custom filter, it will no longer work.
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_ALREADY_EXISTS_ERROR" desc="Error message">
Scriptlet already exists.
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_INVALID_NAME_ERROR" desc="Error message">
Invalid scriptlet name.
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_NOT_FOUND_ERROR" desc="Error message">
Scriptlet is not found.
</message>
<message name="IDS_BRAVE_ADBLOCK_CUSTOM_SCRIPTLET_WARNING" desc="A warning message in Scriptlet editor.">
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.
</message>

<message name="IDS_SETTINGS_BRAVE_SHORTCUTS_TITLE" desc="The text label for the shortcuts settings page">
Shortcuts
Expand Down
11 changes: 11 additions & 0 deletions browser/about_flags.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions browser/brave_profile_prefs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
259 changes: 259 additions & 0 deletions browser/brave_shields/ad_block_custom_resources_browsertest.cc
Original file line number Diff line number Diff line change
@@ -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"));
}
4 changes: 3 additions & 1 deletion browser/brave_shields/ad_block_pref_service_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -47,7 +48,8 @@ AdBlockPrefServiceFactory::BuildServiceInstanceForBrowserContext(
Profile* profile = Profile::FromBrowserContext(context);

auto service = std::make_unique<AdBlockPrefService>(
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(
Expand Down
11 changes: 7 additions & 4 deletions browser/brave_shields/ad_block_service_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,7 @@ void AdBlockServiceTest::UpdateAdBlockResources(const std::string& resources) {
brave_shields::AdBlockService* service =
g_brave_browser_process->ad_block_service();

static_cast<brave_shields::AdBlockDefaultResourceProvider*>(
service->resource_provider())
->OnComponentReady(component_path);
service->default_resource_provider()->OnComponentReady(component_path);
}

void AdBlockServiceTest::UpdateAdBlockInstanceWithRules(
Expand All @@ -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 =
Expand Down Expand Up @@ -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);
}
Expand Down
Loading

0 comments on commit 1f0e2fc

Please sign in to comment.