[Shields UI] Add blocked resource favicons to panel (#34798)
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user