Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Custom Scriptlet section in the settings page. #25999

Merged
merged 21 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
boocmp marked this conversation as resolved.
Show resolved Hide resolved
<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
Loading