Skip to content

[PSST] Show infobar when the feature availability is detected for the website#34174

Open
vadimstruts wants to merge 22 commits intomasterfrom
52731-psst-create-consent-ui-elements
Open

[PSST] Show infobar when the feature availability is detected for the website#34174
vadimstruts wants to merge 22 commits intomasterfrom
52731-psst-create-consent-ui-elements

Conversation

@vadimstruts
Copy link
Collaborator

@vadimstruts vadimstruts commented Feb 25, 2026

Resolves brave/brave-browser#52731

Added display of the infobar when PSST feature availability is detected for the website.
When the user clicks the Review button on the infobar, we plan to show a WebUI modal dialog, which will be added in follow up.

2026-02-25.14-19-11.mp4

@vadimstruts vadimstruts linked an issue Feb 25, 2026 that may be closed by this pull request
@vadimstruts vadimstruts force-pushed the 52731-psst-create-consent-ui-elements branch from 463f997 to 96efa72 Compare February 25, 2026 01:42
@github-actions github-actions bot added the CI/storybook-url Deploy storybook and provide a unique URL for each build label Feb 25, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

📋 Code Owners Summary

21 file(s) changed, 2 with assigned owners

2 team(s) affected: @brave/chromium-src-reviewers, @brave/string-reviewers-team


Owners and Their Files

@brave/string-reviewers-team — 1 file(s)

@brave/chromium-src-reviewers — 1 file(s)

@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

@vadimstruts vadimstruts marked this pull request as ready for review February 25, 2026 13:22
@vadimstruts vadimstruts requested review from a team as code owners February 25, 2026 13:22
@vadimstruts vadimstruts requested a review from boocmp February 25, 2026 15:03
base::fixed_flat_set<std::string_view, 0>{};
#endif

inline constexpr char kBravePsstHost[] = "psst";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move theses constants into the component that uses them. Changes in this file are deprecated and useless :)

void AddLocalizedStrings(content::WebUIDataSource* source) {
DCHECK(source);
static constexpr webui::LocalizedString kLocalizedStrings[] = {
{"bravePsstDialogTitle", IDS_PSST_CONSENT_DIALOG_TITLE},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets use the new strings literals generation system formatter_data="webui=<webui name>" in grdp?
Doc ref: https://sourcegraph.com/github.com/brave/brave-core/-/blob/docs/webui_strings_explainer.md?L1-2

return GURL(kBraveUIPsstURL);
}

void PsstWebDialogDelegate::OnDialogClosed(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like unused. If you plan to use this in the future, then use RegisterOnDialogClosedCallback instead.


void PsstWebDialogDelegate::OnCloseContents(content::WebContents* /* source */,
bool* out_close_dialog) {
*out_close_dialog = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this method and use set_can_close(true) in the c-tor instead.


namespace {

// inline constexpr char kUserScriptResultTaskItemUrlPropName[] = "url";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean up ?

void BravePsstDialogUI::CreatePsstConsentHandler(
::mojo::PendingReceiver<psst::mojom::PsstConsentHelper> psst_consent_helper,
::mojo::PendingRemote<psst::mojom::PsstConsentDialog> psst_consent_dialog) {
BrowserWindowInterface* const bwi =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uff, it's always a pain to bind webui to the browser or tab. I don't like getting any active browser, because it should be hard link between the dialog and the tab.

]
}

if (enable_psst) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets move it into the separated build.gn ?

#endif
#endif

#if BUILDFLAG(ENABLE_PSST)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets use ForWebUI approach ?

@github-actions
Copy link
Contributor

Chromium major version is behind target branch (145.0.7632.120 vs 146.0.7680.32). Please rebase.

@github-actions github-actions bot added the chromium-version-mismatch The Chromium version on the PR branch does not match the version on the target branch label Feb 26, 2026
vadimstruts added a commit that referenced this pull request Feb 27, 2026
#34174 (comment)
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
vadimstruts added a commit that referenced this pull request Feb 27, 2026
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
vadimstruts added a commit that referenced this pull request Feb 27, 2026
#34174 (comment)
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
vadimstruts added a commit that referenced this pull request Feb 27, 2026
#34174 (comment)
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
vadimstruts added a commit that referenced this pull request Feb 27, 2026
#34174 (comment)
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
vadimstruts added a commit that referenced this pull request Feb 27, 2026
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
vadimstruts added a commit that referenced this pull request Feb 27, 2026
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
…ct to the next one

Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
#34174 (comment)
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
#34174 (comment)
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
#34174 (comment)
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
#34174 (comment)
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
#34174 (comment)

Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
Signed-off-by: Vadym Struts <vstruts@brave.com>
@vadimstruts vadimstruts force-pushed the 52731-psst-create-consent-ui-elements branch from 369893a to f458ade Compare February 27, 2026 01:18
@github-actions github-actions bot removed the chromium-version-mismatch The Chromium version on the PR branch does not match the version on the target branch label Feb 27, 2026
Copy link
Contributor

@fallaciousreasoning fallaciousreasoning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for the drive by review!


function initialize() {
render(
<ThemeProvider theme={Theme}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we shouldn't need this ThemeProvider as this shouldn't be using any brave-ui

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

// 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 mojom from 'gen/brave/components/psst/common/psst_ui_common.mojom.m.js'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a new pattern for binding mojo handlers from Brave frontend code. PTAL at https://github.com/brave/brave-core/blob/5e7242311c1bf3478dffaeacb1224f021309638c/components/common/api/readme.md

There's also
#32899
if you want an example of an existing WebUI migrating to this new state manager

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

<SettingGrid>
<SettingGridHeaderRow>
<div style={iconContainerStyle}>
{/* <img
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove commented code

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this file can be removed now this is using the WebUI strings. Optionally you can add a strings.ts file like

// You can obtain one at https://mozilla.org/MPL/2.0/.
so you can reference strings via S.MY_STRING instead of typing the full enum name
bindings for grit strings, and an associated Typescript enum.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

someProp: string
}

export default class PsstDlgContainer extends React.Component<Props, {}> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't use class style components

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

import styled from 'styled-components'
// import Button from '$web-components/button'

export const TextSection = styled('div')<{}>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: don't need to specify empty args - additionally, if you're styling a built in element you can write

const Thing = styled.div`
  /* styles */
`

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

`
export const SettingGridRow = styled(LeftAlignedItem)`
padding: 16px;
border-bottom: 1px solid #eee;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably there's a Nala token for this - possibly you want --leo-color-divider-subtle or -strong?

You can import

import { color} from '@brave/leo/tokens/css'

for intellisense colors

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

Comment on lines +44 to +48
export const CheckBoxIconCompleted = styled(Icon)`
--leo-icon-color: #079235;
`
export const CheckBoxIconFailed = styled(Icon)`
--leo-icon-color: #f10a0a;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not use the checkbox component?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

function initialize() {
render(
<ThemeProvider theme={Theme}>
<PsstDlgContainer someProp={'T'} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

someProp='T'? was this for debugging?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

<part file="omnibox_strings.grdp" />
<part file="playlist_strings.grdp" />
<part file="../brave_account/resources/brave_account_strings.grdp" />
<if expr="not (is_android or is_ios)">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this feature have its own buildflag?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will fix it in follow up, as I removed consent dialog from the current PR

@brave-builds
Copy link
Collaborator

A Storybook has been deployed to preview UI for the latest push

@fallaciousreasoning
Copy link
Contributor

fallaciousreasoning commented Feb 27, 2026

also, in the linked video there's no clear delineation between the infobar and the webcontents - has the @brave/sec-team okay'd that?

Additionally, this PR is nearly 3000 lines - is there some way we could split it up to be smaller? At the very least, its probably worth separating out the frontend/backend changes

@diracdeltas
Copy link
Member

also, in the linked video there's no clear delineation between the infobar and the webcontents - has the @brave/sec-team okay'd that?

@ShivanKaul or @fmarier would be in charge of approving that

@ShivanKaul
Copy link
Collaborator

also, in the linked video there's no clear delineation between the infobar and the webcontents - has the @brave/sec-team okay'd that?

@fallaciousreasoning see https://github.com/brave/reviews/issues/1299#issuecomment-3974240534

Signed-off-by: Vadym Struts <vstruts@brave.com>

splitting the pr: remove browser test, will be returned back in follow up

Signed-off-by: Vadym Struts <vstruts@brave.com>
@vadimstruts vadimstruts force-pushed the 52731-psst-create-consent-ui-elements branch from ac659f7 to 74dd8b1 Compare February 27, 2026 19:45
@vadimstruts vadimstruts requested a review from a team as a code owner February 27, 2026 19:45
script_result_dict.FindString(kPolicyScriptResultNextUrlPropName);
if (next_url && !next_url->empty()) {
// Go to next URL
web_contents()->GetController().LoadURL(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GURL(*next_url) is constructed from data returned by an injected script and passed directly to LoadURL without checking is_valid(). Invalid or malicious URLs from script results could cause unexpected behavior. best practice

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@vadimstruts vadimstruts changed the title [PSST] Create consent UI elements [PSST] Show infobar when the feature availability is detected for the website Mar 2, 2026
@vadimstruts
Copy link
Collaborator Author

vadimstruts commented Mar 2, 2026

Additionally, this PR is nearly 3000 lines - is there some way we could split it up to be smaller? At the very least, its probably worth separating out the frontend/backend changes

I removed consent dialog from the PR

Signed-off-by: Vadym Struts <vstruts@brave.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

[puLL-Merge] - brave/brave-core@34174

Description

This PR adds UI presentation capabilities to the PSST (Privacy Settings Suggestions Tool) feature in Brave browser. It introduces an infobar-based consent flow that notifies users when PSST can optimize a site's privacy settings, allowing them to review suggestions before changes are applied. The PR also refactors the navigation observation mechanism (switching from DidFinishNavigation to NavigationEntryCommitted), introduces a polling mechanism for asynchronous policy script results, and adds an observer pattern to the UI delegate for tracking task progress.

Possible Issues

  1. Copyright year is 2026: Files psst_infobar_delegate.cc, psst_infobar_delegate.h, psst_ui_presenter.cc, and psst_ui_presenter.h all have Copyright (c) 2026 — this should be 2025.

  2. Callback not invoked on Cancel: In PsstInfoBarDelegate, Cancel() calls on_accept_callback_ with false, but the delegate only defines BUTTON_OK via GetButtons(). This means Cancel() may never be called by the infobar framework, leaving the callback potentially dangling if the infobar is dismissed (not explicitly accepted or cancelled).

  3. Empty OnUserAcceptedInfobar implementation: The method has TODO-style comments ("save info in preferences that infobar is accepted" / "User declined the infobar") but no actual implementation, meaning consent state is never persisted.

  4. Raw pointer lifetime risk in UiDesktopPresenter: Holds a raw_ptr<content::WebContents> that could outlive the WebContents if the tab is closed while an infobar callback is pending. The weak pointer factory helps for the callback, but there's no guarantee web_contents_ is valid when ShowInfoBar or ShowIcon is called.

  5. Polling mechanism with magic constants: kGetPolicyScriptExecutionDelay (3 seconds) and kRetryCounterDefault (5) are hardcoded. The retry logic in MaybeGetPolicyScriptResult reads localStorage via injected JavaScript, which is fragile and could silently fail or be manipulated by page scripts.

  6. Browser test removed without replacement: The PsstTabWebContentsObserverBrowserTest was deleted entirely, reducing integration test coverage. The unit tests were updated but may not cover real-world browser behavior.

  7. LoadURL with PAGE_TRANSITION_LINK: In OnPolicyScriptResult, navigating to next_url using PAGE_TRANSITION_LINK could be confusing — this is a programmatic navigation, not a user click. This could also trigger infinite loops if the policy script keeps returning new URLs.

  8. Deprecated base::ListValue usage: The code introduces new uses of base::ListValue (which is deprecated/aliased) in the Show() interface and related methods.

Security Hotspots

  1. JavaScript injection from localStorage polling (kGetPolicyScriptResultScript): The script reads from localStorage and parses JSON from page-controlled storage. A malicious page could write crafted data to psst_settings_status_* keys to influence PSST behavior, potentially triggering unintended navigations via the next_url field in OnPolicyScriptResult.

  2. Unvalidated next_url navigation: In OnPolicyScriptResult, the next_url from the policy script result is loaded with only an is_valid() check. There's no origin validation — a compromised or malicious script could redirect the user to any URL, including phishing sites or javascript: URLs (though GURL may reject the latter).

  3. ListValueToStringVector trusts input: No sanitization is performed on the strings extracted from the list, which are later used as URL identifiers for filtering.

Changes

Changes

  • browser/psst/psst_infobar_delegate.{cc,h} (new): Implements a ConfirmInfoBarDelegate for showing PSST consent infobars with accept/cancel callbacks.
  • browser/psst/psst_ui_presenter.{cc,h} (new): Introduces PsstUiPresenter interface and UiDesktopPresenter implementation that shows infobars and icons via content::WebContents.
  • browser/psst/psst_ui_delegate_impl.{cc,h}: Extended with observer pattern, consent checking, infobar integration, and task progress reporting. Show() signature expanded with site_name and tasks.
  • components/psst/browser/content/psst_tab_web_contents_observer.{cc,h}: Switched from DidFinishNavigation to NavigationEntryCommitted, added async polling for policy script results, consent flow integration, and OnUserAcceptedPsstSettings callback chain.
  • components/psst/browser/content/psst_tab_web_contents_observer_unittest.cc: Major test updates including MockNavigationEntry, new matchers, updated test expectations for the consent flow.
  • browser/psst/BUILD.gn: Removed browser_tests target, added new source files and dependencies.
  • chromium_src/components/infobars/core/infobar_delegate.h: Added BRAVE_PSST_INFOBAR_DELEGATE = 512.
  • app/brave_generated_resources.grd: Added localized strings for PSST infobar.
  • test/data/psst/: Added test HTML pages.
sequenceDiagram
    participant User
    participant WebContents
    participant Observer as PsstTabWebContentsObserver
    participant Registry as PsstRuleRegistry
    participant UiDelegate as PsstUiDelegateImpl
    participant Presenter as UiDesktopPresenter
    participant InfoBar as PsstInfoBarDelegate

    WebContents->>Observer: NavigationEntryCommitted()
    Observer->>Observer: Set PsstNavigationData on entry
    WebContents->>Observer: DocumentOnLoadCompleted()
    Observer->>Registry: CheckIfMatch(url)
    Registry-->>Observer: MatchedRule (user_script + policy_script)
    Observer->>WebContents: Execute user_script
    WebContents-->>Observer: OnUserScriptResult(user_id, tasks, site_name)
    Observer->>UiDelegate: GetPsstWebsiteSettings(origin, user_id)
    UiDelegate-->>Observer: nullopt (first visit)
    Observer->>UiDelegate: Show(origin, settings, site_name, tasks, callback)
    UiDelegate->>Presenter: ShowIcon()
    UiDelegate->>Presenter: ShowInfoBar(callback)
    Presenter->>InfoBar: Create(infobar_manager, callback)
    InfoBar->>User: Display infobar
    User->>InfoBar: Click "Review suggestions"
    InfoBar->>Presenter: OnInfobarAccepted(true)
    Presenter->>UiDelegate: OnUserAcceptedInfobar(origin, true)
    UiDelegate->>Observer: ConsentCallback(disabled_checks=[])
    Observer->>Observer: PrepareParametersForPolicyExecution()
    Observer->>WebContents: Execute policy_script with params
    WebContents-->>Observer: OnPolicyScriptResult(result)
    alt Result has retry_counter
        Observer->>Observer: MaybeGetPolicyScriptResult (poll localStorage)
    else Result has psst data
        Observer->>UiDelegate: UpdateTasks(progress, tasks, status)
        UiDelegate->>UiDelegate: Notify Observers
        Observer->>WebContents: LoadURL(next_url)
    end
Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/storybook-url Deploy storybook and provide a unique URL for each build puLL-Merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[PSST] Create consent UI elements

7 participants