[Shields UI] Add blocked resource favicons to panel (#34798)

This commit is contained in:
Kevin Smith
2026-03-24 09:33:47 -04:00
committed by GitHub
parent 92aae1ccc6
commit 6a07646ae0
11 changed files with 212 additions and 14 deletions
@@ -8,11 +8,14 @@
#include <utility>
#include "base/check.h"
#include "base/task/sequenced_task_runner.h"
#include "brave/browser/brave_browser_process.h"
#include "brave/browser/ui/webui/webcompat_reporter/webcompat_reporter_dialog.h"
#include "brave/components/brave_shields/content/browser/ad_block_service.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/top_chrome/top_chrome_web_ui_controller.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon_base/favicon_types.h"
#include "content/public/browser/navigation_controller.h"
using brave_shields::BraveShieldsTabHelper;
@@ -22,9 +25,11 @@ ShieldsPanelDataHandler::ShieldsPanelDataHandler(
mojo::PendingReceiver<brave_shields::mojom::DataHandler>
data_handler_receiver,
TopChromeWebUIController* webui_controller,
TabStripModel* tab_strip_model)
TabStripModel* tab_strip_model,
favicon::FaviconService* favicon_service)
: data_handler_receiver_(this, std::move(data_handler_receiver)),
webui_controller_(webui_controller) {
webui_controller_(webui_controller),
favicon_service_(favicon_service) {
DCHECK(tab_strip_model);
tab_strip_model->AddObserver(this);
@@ -226,6 +231,27 @@ void ShieldsPanelDataHandler::AreAnyBlockedElementsPresent(
active_shields_data_controller_->web_contents()->GetURL().host()));
}
void ShieldsPanelDataHandler::IsResourceFaviconAvailable(
const GURL& url,
IsResourceFaviconAvailableCallback callback) {
if (!favicon_service_) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
favicon_service_->GetRawFaviconForPageURL(
url, {favicon_base::IconType::kFavicon},
/*desired_size_in_pixel=*/0, /*fallback_to_host=*/true,
base::BindOnce(
[](base::OnceCallback<void(bool)> cb,
const favicon_base::FaviconRawBitmapResult& result) {
std::move(cb).Run(result.is_valid());
},
std::move(callback)),
&cancelable_task_tracker_);
}
void ShieldsPanelDataHandler::ResetBlockedElements() {
webui_controller_->embedder()->CloseUI();
@@ -10,6 +10,7 @@
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/task/cancelable_task_tracker.h"
#include "brave/browser/brave_shields/brave_shields_tab_helper.h"
#include "brave/components/brave_shields/core/common/brave_shields_panel.mojom.h"
#include "brave/components/brave_shields/core/common/shields_settings.mojom.h"
@@ -19,9 +20,12 @@
#include "mojo/public/cpp/bindings/remote.h"
class TabStripModel;
class TopChromeWebUIController;
namespace favicon {
class FaviconService;
}
class ShieldsPanelDataHandler
: public brave_shields::mojom::DataHandler,
public brave_shields::BraveShieldsTabHelper::Observer,
@@ -31,7 +35,8 @@ class ShieldsPanelDataHandler
mojo::PendingReceiver<brave_shields::mojom::DataHandler>
data_handler_receiver,
TopChromeWebUIController* webui_controller,
TabStripModel* browser);
TabStripModel* tab_strip_model,
favicon::FaviconService* favicon_service);
ShieldsPanelDataHandler(const ShieldsPanelDataHandler&) = delete;
ShieldsPanelDataHandler& operator=(const ShieldsPanelDataHandler&) = delete;
@@ -61,6 +66,9 @@ class ShieldsPanelDataHandler
void ResetBlockedElements() override;
void AreAnyBlockedElementsPresent(
AreAnyBlockedElementsPresentCallback callback) override;
void IsResourceFaviconAvailable(
const GURL& url,
IsResourceFaviconAvailableCallback callback) override;
private:
void UpdateSiteBlockInfo();
@@ -78,11 +86,12 @@ class ShieldsPanelDataHandler
mojo::Receiver<brave_shields::mojom::DataHandler> data_handler_receiver_;
mojo::Remote<brave_shields::mojom::UIHandler> ui_handler_remote_;
raw_ptr<TopChromeWebUIController> const webui_controller_ = nullptr;
raw_ptr<brave_shields::BraveShieldsTabHelper>
active_shields_data_controller_ = nullptr;
brave_shields::mojom::SiteBlockInfo site_block_info_;
base::CancelableTaskTracker cancelable_task_tracker_;
raw_ptr<TopChromeWebUIController> const webui_controller_; // not owned
raw_ptr<brave_shields::BraveShieldsTabHelper>
active_shields_data_controller_ = nullptr; // not owned
raw_ptr<favicon::FaviconService> favicon_service_; // not owned
};
#endif // BRAVE_BROWSER_UI_WEBUI_BRAVE_SHIELDS_SHIELDS_PANEL_DATA_HANDLER_H_
@@ -19,6 +19,7 @@
#include "brave/components/constants/pref_names.h"
#include "brave/components/constants/webui_url_constants.h"
#include "brave/components/webcompat/core/common/features.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
@@ -112,8 +113,11 @@ void ShieldsPanelUI::CreatePanelHandler(
std::move(panel_receiver), this, profile);
auto* browser = webui::GetBrowserWindowInterface(web_ui()->GetWebContents());
CHECK(browser);
auto* favicon_service = FaviconServiceFactory::GetForProfile(
profile, ServiceAccessType::EXPLICIT_ACCESS);
data_handler_ = std::make_unique<ShieldsPanelDataHandler>(
std::move(data_handler_receiver), this, browser->GetTabStripModel());
std::move(data_handler_receiver), this, browser->GetTabStripModel(),
favicon_service);
}
ShieldsPanelUIConfig::ShieldsPanelUIConfig()
@@ -65,6 +65,10 @@ interface DataHandler {
// Checks is there any element blocked (by Element blocker feature)
// on the current page
AreAnyBlockedElementsPresent() => (bool is_available);
// Returns a value indicating whether a favicon is available for the
// specified page URL.
IsResourceFaviconAvailable(url.mojom.Url url) => (bool is_available);
};
struct SiteBlockInfo {
@@ -232,7 +232,11 @@ export function createShieldsApi(opts: {
actions: {
...actionsFor(panelHandler, ['closeUI', 'showUI']),
...actionsFor(dataHandler, ['openWebCompatWindow', 'updateFavicon']),
...actionsFor(dataHandler, [
'openWebCompatWindow',
'updateFavicon',
'isResourceFaviconAvailable',
]),
openTab,
},
})
@@ -97,6 +97,10 @@ function createMockDataHandler(): Closable<DataHandlerInterface> {
resetBlockedElements() {},
openWebCompatWindow() {},
updateFavicon() {},
isResourceFaviconAvailable(url) {
return Promise.resolve({ isAvailable: true })
},
})
}
@@ -9,6 +9,7 @@ import { color, font } from '@brave/leo/tokens/css/variables'
export const style = scoped.css`
& {
background: ${color.container.background};
color-scheme: light dark;
}
`
@@ -79,6 +79,49 @@ export const style = scoped.css`
}
}
.blocked-items {
display: flex;
align-items: center;
flex-direction: row-reverse;
}
.blocked-favicon {
--leo-icon-size: 26px;
display: flex;
align-items: center;
position: relative;
background: ${color.container.background};
border-radius: 50%;
padding: 4px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.20);
z-index: 1;
margin: 0 -6px;
&:first-child {
margin-inline-end: 0;
}
&:last-child {
margin-inline-start: 0;
}
.favicon {
height: 18px;
width: 18px;
display: block;
margin: 0 auto;
}
leo-icon {
color: ${color.neutral[50]};
position: absolute;
inset: 0;
display: block;
z-index: 1;
}
}
.report-prompt {
padding: 12px 16px;
border-radius: 16px;
@@ -5,10 +5,12 @@
import * as React from 'react'
import Button from '@brave/leo/react/button'
import Icon from '@brave/leo/react/icon'
import Toggle from '@brave/leo/react/toggle'
import { formatString } from '$web-common/formatString'
import { useShieldsApi } from '../api/shields_api_context'
import { useResourceFaviconUrls } from './use_resource_favicon_urls'
import { getString } from './strings'
import { style } from './main_card.style'
@@ -39,9 +41,12 @@ export function MainCard() {
}
return (
<div className='block-info'>
<div className='items'></div>
<div className='count'>{trackersAndAdsBlocked}</div>
<span>{getString('BRAVE_SHIELDS_TRACKERS_ADS_BLOCKED')}</span>
<div className='blocked-items'>
<BlockedFavicons />
</div>
{formatString(getString('BRAVE_SHIELDS_TRACKERS_ADS_BLOCKED'), {
$1: () => <div className='count'>{trackersAndAdsBlocked}</div>,
})}
</div>
)
}
@@ -82,3 +87,33 @@ export function MainCard() {
</div>
)
}
function BlockedFavicons() {
const api = useShieldsApi()
const { data: siteBlockInfo } = api.useGetSiteBlockInfo()
const adsList = siteBlockInfo?.adsList ?? []
const urls = adsList.map((entry) => entry.url)
const faviconUrls = useResourceFaviconUrls(urls, {
maxQueries: 16,
maxResults: 3,
})
return (
<>
{faviconUrls.map((url) => (
<div
className='blocked-favicon'
key={url}
>
<img
className='favicon'
alt={url}
src={url}
/>
<Icon name='disable-outline' />
</div>
))}
</>
)
}
@@ -0,0 +1,68 @@
/* Copyright (c) 2026 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 * as React from 'react'
import { useShieldsApi } from '../api/shields_api_context'
interface Opts {
maxResults: number
maxQueries: number
}
export function useResourceFaviconUrls(urls: string[], opts: Opts) {
const api = useShieldsApi()
const [pageUrls, setPageUrls] = React.useState<string[]>([])
// Gets the first `opts.maxQueries` deduped empty-path URLs from `urls`.
const queryUrls = React.useMemo(() => {
const set = new Set<string>()
for (const url of urls) {
if (set.size >= opts.maxQueries) {
break
}
try {
const parsed = new URL(url)
set.add(`${parsed.protocol}//${parsed.host}/`)
} catch {}
}
return [...set.values()]
}, [urls.join('\n')])
React.useEffect(() => {
let cancelled = false
// Finds the first `opts.maxResults` favicon page URLs that have a match in
// the profile's favicon database.
async function getFaviconUrls() {
const results = []
for (const url of queryUrls) {
if (cancelled || results.length >= opts.maxResults) {
break
}
const { isAvailable } = await api.isResourceFaviconAvailable({ url })
if (isAvailable) {
results.push(url)
}
}
return results
}
getFaviconUrls().then((pageUrls) => {
if (!cancelled) {
setPageUrls(pageUrls)
}
})
return () => {
cancelled = true
}
}, [queryUrls])
return pageUrls.map(
(pageUrl: string) =>
`chrome://favicon2/?size=32&pageUrl=${encodeURIComponent(pageUrl)}`,
)
}
@@ -41,7 +41,7 @@
</message>
<message name="IDS_BRAVE_SHIELDS_TRACKERS_ADS_BLOCKED" desc="Summary text for blocked trackers and ads." formatter_data="webui=BraveShields">
Trackers, ads, and more blocked.
<ph name="BLOCK_COUNT">$1</ph> trackers, ads, and more blocked.
</message>
<message name="IDS_BRAVE_SHIELDS_TRACKERS_ADS_BLOCKED_TITLE" desc="Title of the trackers and ads blocked details view" formatter_data="webui=BraveShields">