LibWebView: Share content blocker lists as buffers
Add a repeatable blocker-list option that reads local list files in the browser process. The files are concatenated into one buffer and shared with WebContent through the content blocker IPC path when view options are applied. Parse the buffer in WebContent and reject malformed UTF-8 without replacing the currently installed rules.
This commit is contained in:
parent
c974e616c0
commit
6be5e80025
9 changed files with 100 additions and 5 deletions
|
|
@ -4,10 +4,13 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Checked.h>
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Time.h>
|
||||
#include <LibCore/AnonymousBuffer.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/Environment.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibCore/TimeZoneWatcher.h>
|
||||
|
|
@ -161,6 +164,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
bool disable_http_memory_cache = false;
|
||||
bool disable_http_disk_cache = false;
|
||||
bool disable_content_blocker = false;
|
||||
Vector<StringView> content_blocker_list_paths;
|
||||
Optional<StringView> resource_substitution_map_path;
|
||||
bool enable_autoplay = false;
|
||||
bool expose_experimental_interfaces = false;
|
||||
|
|
@ -234,6 +238,19 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
args_parser.add_option(disable_http_memory_cache, "Disable HTTP memory cache", "disable-http-memory-cache");
|
||||
args_parser.add_option(disable_http_disk_cache, "Disable HTTP disk cache", "disable-http-disk-cache");
|
||||
args_parser.add_option(disable_content_blocker, "Disable content blocker", "disable-content-blocker");
|
||||
args_parser.add_option(Core::ArgsParser::Option {
|
||||
.argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
|
||||
.help_string = "Path to a content blocker list. May be specified multiple times.",
|
||||
.long_name = "content-blocker-list",
|
||||
.value_name = "path",
|
||||
.accept_value = [&](StringView value) {
|
||||
if (value.is_empty())
|
||||
return false;
|
||||
|
||||
content_blocker_list_paths.append(value);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
args_parser.add_option(enable_autoplay, "Enable multimedia autoplay", "enable-autoplay");
|
||||
args_parser.add_option(expose_experimental_interfaces, "Expose experimental IDL interfaces", "expose-experimental-interfaces");
|
||||
args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object");
|
||||
|
|
@ -309,6 +326,11 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
if (profile_process.has_value())
|
||||
profile_process_type = process_type_from_name(*profile_process);
|
||||
|
||||
Vector<ByteString> content_blocker_list_paths_as_byte_strings;
|
||||
TRY(content_blocker_list_paths_as_byte_strings.try_ensure_capacity(content_blocker_list_paths.size()));
|
||||
for (auto path : content_blocker_list_paths)
|
||||
content_blocker_list_paths_as_byte_strings.unchecked_append(path);
|
||||
|
||||
// Disable site isolation when debugging WebContent. Otherwise, the process swap may interfere with the gdb session.
|
||||
if (debug_process_types.contains_slow(ProcessType::WebContent))
|
||||
disable_site_isolation = true;
|
||||
|
|
@ -331,6 +353,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
: OptionalNone()),
|
||||
.devtools_port = devtools_port,
|
||||
.enable_content_blocker = disable_content_blocker ? EnableContentBlocker::No : EnableContentBlocker::Yes,
|
||||
.content_blocker_list_paths = move(content_blocker_list_paths_as_byte_strings),
|
||||
};
|
||||
|
||||
if (screenshot_delay.has_value())
|
||||
|
|
@ -387,6 +410,8 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
if (m_web_content_options.file_scheme_urls_have_tuple_origins == FileSchemeUrlsHaveTupleOrigins::Yes)
|
||||
URL::set_file_scheme_urls_have_tuple_origins();
|
||||
|
||||
TRY(load_content_blocker_lists());
|
||||
|
||||
initialize_actions();
|
||||
|
||||
m_event_loop = create_platform_event_loop();
|
||||
|
|
@ -395,6 +420,39 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> Application::load_content_blocker_lists()
|
||||
{
|
||||
if (m_browser_options.content_blocker_list_paths.is_empty())
|
||||
return {};
|
||||
|
||||
Checked<size_t> total_size = 0;
|
||||
for (auto const& path : m_browser_options.content_blocker_list_paths) {
|
||||
auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read));
|
||||
total_size += TRY(file->size());
|
||||
total_size += 1;
|
||||
}
|
||||
|
||||
if (total_size.has_overflow())
|
||||
return Error::from_string_literal("Content blocker lists are too large");
|
||||
|
||||
auto blocker_list_buffer = TRY(Core::AnonymousBuffer::create_with_size(total_size.value()));
|
||||
auto bytes = Bytes { blocker_list_buffer.data<u8>(), blocker_list_buffer.size() };
|
||||
size_t offset = 0;
|
||||
|
||||
for (auto const& path : m_browser_options.content_blocker_list_paths) {
|
||||
auto file = TRY(Core::File::open(path, Core::File::OpenMode::Read));
|
||||
auto file_size = TRY(file->size());
|
||||
TRY(file->read_until_filled(bytes.slice(offset, file_size)));
|
||||
offset += file_size;
|
||||
bytes[offset++] = '\n';
|
||||
}
|
||||
VERIFY(offset == bytes.size());
|
||||
|
||||
m_content_blocker_list_buffer = move(blocker_list_buffer);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Application::open_url_in_new_tab(URL::URL const& url, Web::HTML::ActivateTab activate_tab) const
|
||||
{
|
||||
if (auto view = open_blank_new_tab(activate_tab); view.has_value())
|
||||
|
|
@ -1242,6 +1300,8 @@ void Application::apply_view_options(Badge<ViewImplementation>, ViewImplementati
|
|||
view.debug_request("set-line-box-borders"sv, m_show_line_box_borders_action->checked() ? "on"sv : "off"sv);
|
||||
view.debug_request("scripting"sv, m_enable_scripting_action->checked() ? "on"sv : "off"sv);
|
||||
view.debug_request("content-blocking"sv, m_enable_content_blocking_action->checked() ? "on"sv : "off"sv);
|
||||
if (m_content_blocker_list_buffer.has_value())
|
||||
view.set_content_blockers(*m_content_blocker_list_buffer);
|
||||
view.debug_request("block-pop-ups"sv, m_block_pop_ups_action->checked() ? "on"sv : "off"sv);
|
||||
view.debug_request("spoof-user-agent"sv, m_user_agent_string);
|
||||
view.debug_request("navigator-compatibility-mode"sv, m_navigator_compatibility_mode);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <AK/Function.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <LibCore/AnonymousBuffer.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Forward.h>
|
||||
#include <LibDatabase/Forward.h>
|
||||
|
|
@ -209,6 +210,7 @@ private:
|
|||
ErrorOr<void> launch_request_server();
|
||||
ErrorOr<void> launch_image_decoder_server();
|
||||
ErrorOr<void> launch_devtools_server();
|
||||
ErrorOr<void> load_content_blocker_lists();
|
||||
|
||||
void initialize_actions();
|
||||
|
||||
|
|
@ -271,6 +273,7 @@ private:
|
|||
BrowserOptions m_browser_options;
|
||||
RequestServerOptions m_request_server_options;
|
||||
WebContentOptions m_web_content_options;
|
||||
Optional<Core::AnonymousBuffer> m_content_blocker_list_buffer;
|
||||
|
||||
RefPtr<Requests::RequestClient> m_request_server_client;
|
||||
RefPtr<ImageDecoderClient::Client> m_image_decoder_client;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ struct BrowserOptions {
|
|||
Optional<DNSSettings> dns_settings {};
|
||||
Optional<u16> devtools_port;
|
||||
EnableContentBlocker enable_content_blocker { EnableContentBlocker::Yes };
|
||||
Vector<ByteString> content_blocker_list_paths {};
|
||||
};
|
||||
|
||||
enum class HTTPDiskCacheMode {
|
||||
|
|
|
|||
|
|
@ -566,6 +566,11 @@ void ViewImplementation::debug_request(ByteString const& request, ByteString con
|
|||
client().async_debug_request(page_id(), request, argument);
|
||||
}
|
||||
|
||||
void ViewImplementation::set_content_blockers(Core::AnonymousBuffer const& patterns)
|
||||
{
|
||||
client().async_set_content_blockers(page_id(), patterns);
|
||||
}
|
||||
|
||||
void ViewImplementation::run_javascript(String const& js_source)
|
||||
{
|
||||
client().async_run_javascript(page_id(), js_source);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <AK/Queue.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <LibCore/AnonymousBuffer.h>
|
||||
#include <LibCore/Forward.h>
|
||||
#include <LibCore/Promise.h>
|
||||
#include <LibCore/SharedVersion.h>
|
||||
|
|
@ -143,6 +144,7 @@ public:
|
|||
void request_style_sheet_source(Web::CSS::StyleSheetIdentifier const&);
|
||||
|
||||
void debug_request(ByteString const& request, ByteString const& argument = {});
|
||||
void set_content_blockers(Core::AnonymousBuffer const& patterns);
|
||||
|
||||
void run_javascript(String const&);
|
||||
void js_console_input(String const&);
|
||||
|
|
|
|||
|
|
@ -1144,11 +1144,33 @@ void ConnectionFromClient::paste(u64 page_id, Utf16String text)
|
|||
page->page().focused_navigable().paste(text);
|
||||
}
|
||||
|
||||
void ConnectionFromClient::set_content_blockers(u64 page_id, Vector<String> patterns)
|
||||
static ErrorOr<Vector<String>> parse_content_blocker_patterns(Core::AnonymousBuffer const& patterns_buffer)
|
||||
{
|
||||
Vector<String> patterns;
|
||||
|
||||
for (auto line : StringView { patterns_buffer.bytes() }.split_view('\n', SplitBehavior::Nothing)) {
|
||||
if (line.ends_with('\r'))
|
||||
line = line.substring_view(0, line.length() - 1);
|
||||
if (line.is_empty())
|
||||
continue;
|
||||
|
||||
patterns.append(TRY(String::from_utf8(line)));
|
||||
}
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
void ConnectionFromClient::set_content_blockers(u64 page_id, Core::AnonymousBuffer patterns_buffer)
|
||||
{
|
||||
auto patterns_or_error = parse_content_blocker_patterns(patterns_buffer);
|
||||
if (patterns_or_error.is_error()) {
|
||||
dbgln("Failed to set content blockers: {}", patterns_or_error.error());
|
||||
return;
|
||||
}
|
||||
|
||||
auto& blocker = Web::ContentBlocker::the();
|
||||
auto had_cosmetic_rules = blocker.has_cosmetic_rules();
|
||||
blocker.set_patterns(patterns).release_value_but_fixme_should_propagate_errors();
|
||||
blocker.set_patterns(patterns_or_error.value()).release_value_but_fixme_should_propagate_errors();
|
||||
|
||||
if (had_cosmetic_rules || blocker.has_cosmetic_rules()) {
|
||||
if (auto page = this->page(page_id); page.has_value())
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <AK/HashMap.h>
|
||||
#include <AK/Queue.h>
|
||||
#include <AK/SourceLocation.h>
|
||||
#include <LibCore/AnonymousBuffer.h>
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibIPC/ConnectionFromClient.h>
|
||||
#include <LibJS/Forward.h>
|
||||
|
|
@ -107,7 +108,7 @@ private:
|
|||
virtual void clone_dom_node(u64 page_id, Web::UniqueNodeID node_id) override;
|
||||
virtual void remove_dom_node(u64 page_id, Web::UniqueNodeID node_id) override;
|
||||
|
||||
virtual void set_content_blockers(u64 page_id, Vector<String> patterns) override;
|
||||
virtual void set_content_blockers(u64 page_id, Core::AnonymousBuffer patterns) override;
|
||||
virtual void set_autoplay_allowed_on_all_websites(u64 page_id) override;
|
||||
virtual void set_autoplay_allowlist(u64 page_id, Vector<String> allowlist) override;
|
||||
virtual void set_proxy_mappings(u64 page_id, Vector<ByteString>, HashMap<ByteString, size_t>) override;
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ endpoint WebContentServer
|
|||
find_in_page_next_match(u64 page_id) =|
|
||||
find_in_page_previous_match(u64 page_id) =|
|
||||
|
||||
set_content_blockers(u64 page_id, Vector<String> patterns) =|
|
||||
set_content_blockers(u64 page_id, Core::AnonymousBuffer patterns) =|
|
||||
set_autoplay_allowed_on_all_websites(u64 page_id) =|
|
||||
set_autoplay_allowlist(u64 page_id, Vector<String> allowlist) =|
|
||||
set_proxy_mappings(u64 page_id, Vector<ByteString> proxies, HashMap<ByteString, size_t> mappings) =|
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "TestWebView.h"
|
||||
|
||||
#include <LibCore/AnonymousBuffer.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/ShareableBitmap.h>
|
||||
|
||||
|
|
@ -27,7 +28,7 @@ TestWebView::TestWebView(Core::AnonymousBuffer theme, Web::DevicePixelSize viewp
|
|||
|
||||
void TestWebView::clear_content_blockers()
|
||||
{
|
||||
client().async_set_content_blockers(m_client_state.page_index, {});
|
||||
client().async_set_content_blockers(m_client_state.page_index, MUST(Core::AnonymousBuffer::create_with_size(0)));
|
||||
}
|
||||
|
||||
pid_t TestWebView::web_content_pid() const
|
||||
|
|
|
|||
Loading…
Reference in a new issue