diff --git a/components/brave_shields/resources/panel_new/components/advanced_settings.style.ts b/components/brave_shields/resources/panel_new/components/advanced_settings.style.ts new file mode 100644 index 00000000000..709b3e23b56 --- /dev/null +++ b/components/brave_shields/resources/panel_new/components/advanced_settings.style.ts @@ -0,0 +1,98 @@ +/* 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 { color, font } from '@brave/leo/tokens/css/variables' +import { scoped } from '$web-common/scoped_css' + +export const style = scoped.css` + + & { + --leo-icon-size: 20px; + + padding: 8px; + display: flex; + flex-direction: column; + gap: 8px; + + > * { + display: flex; + align-items: center; + gap: 8px; + padding: 0 4px 0 8px; + + > :last-child { + padding: 4px; + min-width: 28px; + margin-inline-start: -4px; + } + + > button { + border-radius: 8px; + } + + > button:hover { + background: ${color.button.hover}; + } + + > button:disabled { + visibility: hidden; + } + } + } + + .toggle { + flex: 1 1 auto; + display: flex; + align-items: center; + gap: 8px; + padding: 11px 8px; + + & > *:last-child { + margin-inline-start: auto; + } + } + + leo-dropdown { + flex: 1 1 auto; + + [slot=value] { + display: flex; + width: 100%; + align-items: center; + gap: 8px; + } + } + + .counter { + border-radius: 6px; + padding: 2px 4px; + background-color: ${color.neutral[10]}; + font: ${font.small.semibold}; + color: ${color.neutral[60]}; + } + + .block-scripts-text { + > * { + display: flex; + align-items: center; + gap: 8px; + } + + .note { + font: ${font.small.regular}; + color: ${color.text.tertiary}; + } + } + + .block-elements { + padding: 11px 8px; + + button { + color: ${color.text.interactive}; + font: ${font.small.regular}; + text-decoration: underline; + } + } +` diff --git a/components/brave_shields/resources/panel_new/components/advanced_settings.tsx b/components/brave_shields/resources/panel_new/components/advanced_settings.tsx new file mode 100644 index 00000000000..8e90088b5b0 --- /dev/null +++ b/components/brave_shields/resources/panel_new/components/advanced_settings.tsx @@ -0,0 +1,414 @@ +/* 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 Dropdown from '@brave/leo/react/dropdown' +import Icon from '@brave/leo/react/icon' +import Toggle from '@brave/leo/react/toggle' + +import { useShieldsApi } from '../api/shields_api_context' +import { StringKey, getString } from './strings' + +import { + AdBlockMode, + HttpsUpgradeMode, + FingerprintMode, + CookieBlockMode, + ContentSettingSource, +} from 'gen/brave/components/brave_shields/core/common/shields_settings.mojom.m.js' + +import { ContentSetting } from 'gen/components/content_settings/core/common/content_settings.mojom.m' + +import { style } from './advanced_settings.style' + +interface Props { + showFingerprintingDetails: () => void + showAdsBlocked: () => void + showScriptsBlocked: () => void +} + +export function AdvancedSettings(props: Props) { + const api = useShieldsApi() + const { data: siteBlockInfo } = api.useGetSiteBlockInfo() + const showAdvancedView = api.useGetAdvancedViewEnabledData() + + const shieldsEnabled = siteBlockInfo?.isBraveShieldsEnabled + const adblockOnlyEnabled = siteBlockInfo?.isBraveShieldsAdBlockOnlyModeEnabled + + if (!shieldsEnabled || !showAdvancedView || adblockOnlyEnabled) { + return null + } + + return ( +
+ + + + + + + +
+ ) +} + +function AdBlockControl(props: { showDetails: () => void }) { + const api = useShieldsApi() + const { data: siteSettings } = api.useGetSiteSettings() + const { data: siteBlockInfo } = api.useGetSiteBlockInfo() + + if (!siteSettings || !siteBlockInfo) { + return null + } + + const adBlockMode = siteSettings.adBlockMode + const adsBlocked = siteBlockInfo.adsList.length + const isManaged = siteBlockInfo.isBraveShieldsManaged + + return ( +
+ + { + withEnumValue(value, (mode) => api.setAdBlockMode([mode])) + }} + > + + {getString(getAdBlockModeLabel(adBlockMode))} + + + + {getString(getAdBlockModeLabel(AdBlockMode.AGGRESSIVE))} + + + {getString(getAdBlockModeLabel(AdBlockMode.STANDARD))} + + + {getString(getAdBlockModeLabel(AdBlockMode.ALLOW))} + + + +
+ ) +} + +function getAdBlockModeLabel(mode: number | undefined) { + switch (mode) { + case AdBlockMode.AGGRESSIVE: + return 'BRAVE_SHIELDS_TRACKERS_AND_ADS_BLOCKED_AGG' + case AdBlockMode.STANDARD: + return 'BRAVE_SHIELDS_TRACKERS_AND_ADS_BLOCKED_STD' + case AdBlockMode.ALLOW: + return 'BRAVE_SHIELDS_TRACKERS_AND_ADS_ALLOW_ALL' + } + throw new Error(`Unrecognized AdBlockMode value: ${mode}`) +} + +function HttpsUpgradeControls() { + const api = useShieldsApi() + const { data: siteSettings } = api.useGetSiteSettings() + const isHttpsByDefaultEnabled = api.useIsHttpsByDefaultEnabledData() + const isTorProfile = api.useIsTorProfileData() + + if (!siteSettings) { + return null + } + + const httpsUpgradeMode = siteSettings.httpsUpgradeMode + + if (!isHttpsByDefaultEnabled || isTorProfile) { + return null + } + + return ( +
+ + { + withEnumValue(value, (mode) => api.setHttpsUpgradeMode([mode])) + }} + > + + {getString('BRAVE_SHIELDS_HTTPS_UPGRADE_MODE_STRICT')} + + + {getString('BRAVE_SHIELDS_HTTPS_UPGRADE_MODE_STANDARD')} + + + {getString('BRAVE_SHIELDS_HTTPS_UPGRADE_MODE_DISABLED')} + + +
+
+ ) +} + +function BlockScriptsControls(props: { showDetails: () => void }) { + const api = useShieldsApi() + const { data: siteBlockInfo } = api.useGetSiteBlockInfo() + const { data: siteSettings } = api.useGetSiteSettings() + + if (!siteBlockInfo || !siteSettings) { + return null + } + + const jsBlocked = siteBlockInfo.blockedJsList.length + const jsAllowed = siteBlockInfo.allowedJsList.length + const isNoscriptEnabled = siteSettings.isNoscriptEnabled + const isManaged = siteBlockInfo.isBraveShieldsManaged + const hasBlockedOrAllowed = jsBlocked > 0 || jsAllowed > 0 + + const overrideStatus = siteSettings.scriptsBlockedOverrideStatus + const overrideSource = overrideStatus?.overrideSource + + const scriptsBlockedEnforced = + overrideStatus + && overrideStatus.status !== ContentSetting.DEFAULT + && overrideSource !== ContentSettingSource.kUser + && overrideSource !== ContentSettingSource.kNone + + return ( +
+ +
+
+
+ {getString('BRAVE_SHIELDS_SCRIPTS_BLOCKED')} + +
+ {scriptsBlockedEnforced && ( +
+ {getString(getEnforcedDescription(overrideSource))} +
+ )} +
+ { + api.setIsNoScriptsEnabled([event.checked]) + }} + /> +
+ +
+ ) +} + +function getEnforcedDescription(overrideSource: number | undefined): StringKey { + switch (overrideSource) { + case ContentSettingSource.kExtension: + return 'BRAVE_SHIELDS_SCRIPTS_BLOCKED_OVERRIDDEN_BY_EXTENSION' + case ContentSettingSource.kPolicy: + return 'BRAVE_SHIELDS_SCRIPTS_BLOCKED_OVERRIDDEN_BY_POLICY' + case ContentSettingSource.kAllowList: + return 'BRAVE_SHIELDS_SCRIPTS_BLOCKED_OVERRIDDEN_BY_ALLOWLIST' + case ContentSettingSource.kSupervised: + return 'BRAVE_SHIELDS_SCRIPTS_BLOCKED_OVERRIDDEN_BY_SUPERVISOR' + case ContentSettingSource.kInstalledWebApp: + return 'BRAVE_SHIELDS_SCRIPTS_BLOCKED_OVERRIDDEN_BY_PWA' + default: + return 'BRAVE_SHIELDS_SCRIPTS_BLOCKED_OVERRIDDEN' + } +} + +function FingerprintingControls(props: { showDetails: () => void }) { + const api = useShieldsApi() + const { data: siteBlockInfo } = api.useGetSiteBlockInfo() + const { data: siteSettings } = api.useGetSiteSettings() + const showStrictMode = api.useShowStrictFingerprintingModeData() + const webcompatExceptionsEnabled = + api.useIsWebcompatExceptionsServiceEnabledData() + + if (!siteBlockInfo || !siteSettings) { + return null + } + + const isManaged = siteBlockInfo.isBraveShieldsManaged + const fingerprintMode = siteSettings.fingerprintMode + const invokedWebcompatCount = siteBlockInfo.invokedWebcompatList.length + + return ( +
+ + {showStrictMode ? ( + { + withEnumValue(value, (mode) => api.setFingerprintMode([mode])) + }} + > + + {getString('BRAVE_SHIELDS_FINGERPRINTING_BLOCKED_AGG')} + + + {getString('BRAVE_SHIELDS_FINGERPRINTING_BLOCKED_STD')} + + + {getString('BRAVE_SHIELDS_FINGERPRINTING_ALLOW_ALL')} + + + ) : ( +
+ {getString('BRAVE_SHIELDS_FINGERPRINTING_BLOCKED_STD')} + { + api.setFingerprintMode([ + event.checked + ? FingerprintMode.STANDARD_MODE + : FingerprintMode.ALLOW_MODE, + ]) + }} + /> +
+ )} + +
+ ) +} + +function CookieBlockControls() { + const api = useShieldsApi() + const { data: siteSettings } = api.useGetSiteSettings() + const { data: siteBlockInfo } = api.useGetSiteBlockInfo() + + if (!siteBlockInfo || !siteSettings) { + return null + } + + const cookieBlockMode = siteSettings.cookieBlockMode + const isManaged = siteBlockInfo.isBraveShieldsManaged + + return ( +
+ + { + withEnumValue(value, (mode) => api.setCookieBlockMode([mode])) + }} + > + + {getString('BRAVE_SHIELDS_COOKIES_BLOCKED')} + + + {getString('BRAVE_SHIELDS_THIRD_PARTY_COOKIES_BLOCKED')} + + + {getString('BRAVE_SHIELDS_COOKIES_ALLOWED_ALL')} + + +
+
+ ) +} + +function ForgetFirstPartyStorageControls() { + const api = useShieldsApi() + const { data: siteSettings } = api.useGetSiteSettings() + const { data: siteBlockInfo } = api.useGetSiteBlockInfo() + const forgetFeatureEnabled = + api.useIsBraveForgetFirstPartyStorageFeatureEnabledData() + + if (!siteBlockInfo || !siteSettings) { + return null + } + + const isForgetEnabled = siteSettings.isForgetFirstPartyStorageEnabled + const isManaged = siteBlockInfo.isBraveShieldsManaged + + if (!forgetFeatureEnabled) { + return null + } + + return ( +
+ +
+ {getString('BRAVE_SHIELDS_FORGET_FIRST_PARTY_STORAGE_LABEL')} + { + api.setForgetFirstPartyStorageEnabled([event.checked]) + }} + /> +
+
+
+ ) +} + +function BlockElementsControls() { + const api = useShieldsApi() + const blockedElementsPresent = api.useAreAnyBlockedElementsPresentData() + + if (!blockedElementsPresent) { + return null + } + + return ( +
+ +
+
{getString('BRAVE_SHIELDS_BLOCK_ELEMENTS')}
+ +
+
+
+ ) +} + +function Counter(props: { count: number }) { + if (props.count <= 0) { + return null + } + return ( + {props.count > 99 ? '99+' : props.count} + ) +} + +function withEnumValue(s: string | undefined, fn: (value: number) => void) { + const value = parseInt(s ?? '', 10) + if (!isNaN(value)) { + fn(value) + } +} diff --git a/components/brave_shields/resources/panel_new/components/app.tsx b/components/brave_shields/resources/panel_new/components/app.tsx index 173aeb39623..33929eb024b 100644 --- a/components/brave_shields/resources/panel_new/components/app.tsx +++ b/components/brave_shields/resources/panel_new/components/app.tsx @@ -8,6 +8,7 @@ import * as React from 'react' import { MainCard } from './main_card' import { Footer } from './footer' import { AdvancedSettingsHeader } from './advanced_settings_header' +import { AdvancedSettings } from './advanced_settings' import { useInitializedStatus } from './use_initialized_status' import { useCacheInvalidator } from './use_cache_invalidator' @@ -61,6 +62,13 @@ function MainView(props: { showView: (view: AppView) => void }) {
+ props.showView('ads-blocked')} + showScriptsBlocked={() => props.showView('scripts-blocked')} + showFingerprintingDetails={() => + props.showView('fingerprinting-details') + } + />
) diff --git a/components/brave_shields/resources/panel_new/components/strings.ts b/components/brave_shields/resources/panel_new/components/strings.ts index 7ed94a5c4cf..24d593d22d1 100644 --- a/components/brave_shields/resources/panel_new/components/strings.ts +++ b/components/brave_shields/resources/panel_new/components/strings.ts @@ -6,6 +6,6 @@ import { getLocale } from '$web-common/locale' import { BraveShieldsStrings } from 'gen/components/grit/brave_components_webui_strings' -type StringKey = keyof typeof BraveShieldsStrings +export type StringKey = keyof typeof BraveShieldsStrings export const getString = <(key: StringKey) => string>getLocale diff --git a/components/resources/brave_shields_strings.grdp b/components/resources/brave_shields_strings.grdp index cca053176d3..af28a052b51 100644 --- a/components/resources/brave_shields_strings.grdp +++ b/components/resources/brave_shields_strings.grdp @@ -132,7 +132,11 @@ Global Settings - + + Block elements + + + Clear all blocked elements @@ -168,31 +172,31 @@ Enable HTTPS - + Block scripts - + (enforced) - + (enforced by extension) - + (enforced by policy) - + (enforced by allowlist) - + (enforced by supervisor) - + (enforced by PWA) @@ -204,23 +208,23 @@ Trackers & ads - + Block trackers & ads - + Aggressively block trackers & ads - + Allow all trackers & ads - + Block third-party cookies - + Forget me when I close this site @@ -244,11 +248,11 @@ Enables the Shields Block Elements feature in private windows. Note that elements blocked in private windows will also be blocked in non-private windows. - + Block all cookies - + Allow all cookies @@ -256,27 +260,27 @@ Fingerprinting blocked - + Block fingerprinting - + Aggressively block fingerprinting - + Allow fingerprinting - + Don't upgrade HTTP connections - + Upgrade connections to HTTPS - + Only connect with HTTPS diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn index 4adf76639de..7ac6656f1a9 100644 --- a/ui/webui/resources/BUILD.gn +++ b/ui/webui/resources/BUILD.gn @@ -181,6 +181,7 @@ leo_icons = [ "bar-chart.svg", "bat-color.svg", "bing-color.svg", + "biometric-login.svg", "bluetooth-off.svg", "bluetooth.svg", "brave-icon-monochrome.svg", @@ -283,6 +284,7 @@ leo_icons = [ "grid04.svg", "hamburger-menu.svg", "hand-coins.svg", + "hand-raised.svg", "hearts.svg", "help-outline.svg", "history.svg", @@ -308,6 +310,7 @@ leo_icons = [ "loading-spinner.svg", "location-off.svg", "location-on.svg", + "lock-dots.svg", "lock-open.svg", "lock-plain.svg", "lock.svg",