Remove Brave News promoted articles dead code on Desktop (#36582)

The Brave News `Promoted Articles` ads pipeline is already disabled via
https://github.com/brave/brave-core/pull/32359.
This PR removes the remaining Desktop frontend code and portions of the mojom
code that no longer had any effect. The remaining mojom code is still required for
Android builds and will be removed in a follow-up when the dead code for Brave
News promoted articles on Android is cleaned up.
This commit is contained in:
Aleksei Seren
2026-05-21 09:51:00 -05:00
committed by GitHub
parent 120f27059e
commit 756e06555c
15 changed files with 34 additions and 261 deletions
@@ -31,20 +31,12 @@ export const errorGettingDataFromBackground = createAction<BackgroundErrorPayloa
*/
export type ReadFeedItemPayload = {
item: BraveNews.FeedItem
isPromoted?: boolean
promotedUUID?: string
openInNewTab?: boolean
}
export const readFeedItem = createAction<ReadFeedItemPayload>('readFeedItem')
export const feedItemViewedCountChanged = createAction<number>('feedItemViewedCountChanged')
export type PromotedItemViewedPayload = {
item: BraveNews.FeedItem
uuid: string
}
export const promotedItemViewed = createAction<PromotedItemViewedPayload>('promotedItemViewed')
export type SetPublisherPrefPayload = {
publisherId: string
enabled: boolean | null
@@ -55,30 +55,8 @@ handler.on<Actions.ReadFeedItemPayload>(
Actions.readFeedItem.getType(),
async (store, payload) => {
const state = store.getState() as ApplicationState
if (payload.isPromoted) {
const promotedArticle = payload.item.promotedArticle
if (!promotedArticle) {
console.error(
'Brave News: readFeedItem payload with invalid promoted article',
payload
)
return
}
if (!payload.promotedUUID) {
console.error(
'Brave News: invalid promotedUUID for readFeedItem',
payload
)
return
}
getBraveNewsController().onPromotedItemVisit(
payload.promotedUUID,
promotedArticle.creativeInstanceId
)
}
const data =
payload.item.article?.data ||
payload.item.promotedArticle?.data ||
payload.item.deal?.data
if (!data) {
console.error(
@@ -104,23 +82,6 @@ handler.on<Actions.ReadFeedItemPayload>(
}
)
handler.on<Actions.PromotedItemViewedPayload>(
Actions.promotedItemViewed.getType(),
async (store, payload) => {
if (!payload.item.promotedArticle) {
console.error(
'Brave News: promotedItemViewed invalid promoted article',
payload
)
return
}
getBraveNewsController().onPromotedItemView(
payload.uuid,
payload.item.promotedArticle.creativeInstanceId
)
}
)
handler.on<number>(
Actions.feedItemViewedCountChanged.getType(),
async (store, payload) => {
@@ -57,10 +57,7 @@ export const ListImageFrame = styled(ImageFrame)`
padding-top: 0;
`
type ImageProps = {
isPromoted?: boolean
}
export const Image = styled('img')<ImageProps>`
export const Image = styled('img')`
box-sizing: border-box;
display: block;
position: absolute;
@@ -72,8 +69,8 @@ export const Image = styled('img')<ImageProps>`
right: 0;
width: 100%;
height: 100%;
object-fit: ${p => p.isPromoted ? 'contain' : 'cover'};
object-position: ${p => p.isPromoted ? 'center center' : 'center top'};
object-fit: cover;
object-position: center top;
background-color: rgba(188,188,188,0.2);
`
@@ -10,7 +10,6 @@ import * as BraveNews from '../../../../../brave_news/browser/resources/shared/a
type Props = {
imageUrl?: string
list?: boolean
isPromoted?: boolean
onLoaded?: () => any
}
@@ -36,7 +35,7 @@ export default function CardImage(props: Props) {
const Frame = props.list ? Card.ListImageFrame : Card.ImageFrame
return (
<Frame data-source={props.imageUrl} isImageLoaded={isImageLoaded}>
<Card.Image isPromoted={props.isPromoted} src={imageUrl} />
<Card.Image src={imageUrl} />
</Frame>
)
}
@@ -67,7 +66,6 @@ export function CardImageFromFeedItem(props: FromFeedItemProps) {
<CardImage
{...baseProps}
imageUrl={imageUrl}
isPromoted={props.isPromoted}
/>
)
}
@@ -3,10 +3,8 @@
// 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 { getLocale } from '$web-common/locale'
import VisibilityTimer from '$web-common/visibilityTimer'
import * as React from 'react'
import { OnPromotedItemViewed, OnReadFeedItem, OnSetPublisherPref } from '../../'
import { OnReadFeedItem, OnSetPublisherPref } from '../../'
import * as BraveNews from '../../../../../../brave_news/browser/resources/shared/api'
import * as Card from '../../cardSizes'
import useReadArticleClickHandler from '../../useReadArticleClickHandler'
@@ -18,8 +16,6 @@ import PublisherMeta from '../PublisherMeta'
type Props = {
onReadFeedItem: OnReadFeedItem
onSetPublisherPref: OnSetPublisherPref
onItemViewed?: OnPromotedItemViewed
isPromoted?: boolean
}
type ArticlesProps = Props & {
@@ -34,40 +30,15 @@ type ArticleProps = Props & {
shouldScrollIntoView?: boolean
}
const promotedInfoUrl = 'https://brave.com/brave-today'
function onClickPromoted (e: React.MouseEvent) {
const openInNewTab = e.ctrlKey || e.metaKey
if (openInNewTab) {
document.open(promotedInfoUrl, '__blank', 'noreferrer')
} else {
window.location.href = promotedInfoUrl
}
e.preventDefault()
}
const LargeArticle = React.forwardRef<HTMLElement, ArticleProps>(function (props: ArticleProps, forwardedRef) {
const { publisher, item } = props
const [cardRef] = useScrollIntoView(props.shouldScrollIntoView || false)
const innerRef = React.useRef<HTMLElement>(null)
const data = item.article?.data || item.promotedArticle?.data
const data = item.article?.data
const uuid = React.useMemo<string | undefined>(function () {
if (props.isPromoted) {
const uuid: string = crypto.randomUUID()
return uuid
}
return undefined
}, [props.isPromoted, data?.url.url])
const onClick = useReadArticleClickHandler(props.onReadFeedItem, { item, isPromoted: props.isPromoted, promotedUUID: uuid })
const onItemViewedRef = React.useRef<Function | null>()
onItemViewedRef.current = props.onItemViewed
? props.onItemViewed.bind(undefined, { item: props.item, uuid })
: null
const onClick = useReadArticleClickHandler(props.onReadFeedItem, { item })
React.useEffect(() => {
if (!innerRef.current) {
@@ -83,24 +54,7 @@ const LargeArticle = React.forwardRef<HTMLElement, ArticleProps>(function (props
ref.current = newRef
}
}
// If asked, detect when card is viewed, and send an action.
if (!props.onItemViewed) {
return
}
const observer = new VisibilityTimer(() => {
const onItemViewed = onItemViewedRef.current
if (onItemViewed) {
onItemViewed()
}
}, 100, innerRef.current)
observer.startTracking()
return () => {
observer.stopTracking()
}
}, [innerRef.current, Boolean(props.onItemViewed)])
}, [innerRef.current])
if (!data) {
return null
@@ -113,7 +67,6 @@ const LargeArticle = React.forwardRef<HTMLElement, ArticleProps>(function (props
<a onClick={onClick} href={data.url.url} ref={cardRef}>
<CardImageFromFeedItem
data={data}
isPromoted={props.isPromoted}
/>
<Card.Content>
<Card.Heading>
@@ -129,21 +82,6 @@ const LargeArticle = React.forwardRef<HTMLElement, ArticleProps>(function (props
onSetPublisherPref={props.onSetPublisherPref}
/>
</Card.Publisher>
{props.isPromoted &&
<Card.PromotedLabel onClick={onClickPromoted} href={promotedInfoUrl}>
<Card.PromotedIcon>
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 9'>
<path
fill='#fff'
fillRule='evenodd'
d='M14.56 4.77a.9.9 0 01-.9-.9v-.83L8.73 7.27a.9.9 0 01-1.23-.05L5.36 5.08 1.47 8.19A.9.9 0 01.2 8.05a.9.9 0 01.14-1.27l4.52-3.62a.9.9 0 011.2.07L8.2 5.35l3.84-3.3h-.18a.9.9 0 110-1.8h2.71c.4 0 .67.26.77.62v.05c.02.08.05.15.05.23v2.72c0 .5-.32.9-.82.9z'
clipRule='evenodd'
/>
</svg>
</Card.PromotedIcon>
{getLocale('braveNewsPromoted')}
</Card.PromotedLabel>
}
</Card.Source>
}
</Card.Content>
@@ -162,7 +100,7 @@ const CardSingleArticleLarge = React.forwardRef<HTMLElement, ArticlesProps>(func
<>
{props.content.map((item, index) => {
const key = `card-key-${index}`
const data = item.article?.data || item.promotedArticle?.data
const data = item.article?.data
// If there is a missing item, return nothing
if (!data) {
return (
@@ -186,8 +124,6 @@ const CardSingleArticleLarge = React.forwardRef<HTMLElement, ArticlesProps>(func
shouldScrollIntoView={shouldScrollIntoView}
onReadFeedItem={props.onReadFeedItem}
onSetPublisherPref={props.onSetPublisherPref}
onItemViewed={props.onItemViewed}
isPromoted={props.isPromoted}
/>
)
})}
@@ -10,7 +10,7 @@ import CardSmall from './cards/_articles/cardArticleMedium'
import CategoryGroup from './cards/categoryGroup'
import PublisherGroup from './cards/publisherGroup'
import CardDeals from './cards/cardDeals'
import { attributeNameCardCount, OnPromotedItemViewed, OnReadFeedItem, OnSetPublisherPref } from './'
import { attributeNameCardCount, OnReadFeedItem, OnSetPublisherPref } from './'
import CardType = BraveNews.CardType
@@ -25,7 +25,6 @@ type Props = {
onReadFeedItem: OnReadFeedItem
onSetPublisherPref: OnSetPublisherPref
onPeriodicCardViews: (element: HTMLElement | null) => void
onPromotedItemViewed: OnPromotedItemViewed
}
function getCard (props: Props, content: BraveNews.FeedPageItem) {
@@ -47,15 +46,7 @@ function getCard (props: Props, content: BraveNews.FeedPageItem) {
onSetPublisherPref={props.onSetPublisherPref}
/>
case CardType.PROMOTED_ARTICLE:
return <CardLarge
isPromoted={true}
content={content.items}
publishers={props.publishers}
articleToScrollTo={props.articleToScrollTo}
onReadFeedItem={props.onReadFeedItem}
onSetPublisherPref={props.onSetPublisherPref}
onItemViewed={props.onPromotedItemViewed}
/>
return null
case CardType.CATEGORY_GROUP:
const categoryName = content.items[0]?.article?.data.categoryName
if (!categoryName) {
@@ -213,7 +213,6 @@ export default function BraveNewsContent(props: Props) {
onReadFeedItem={props.onReadFeedItem}
onPeriodicCardViews={registerCardCountTriggerElement}
onSetPublisherPref={props.onSetPublisherPref}
onPromotedItemViewed={props.onPromotedItemViewed}
/>
)
})
@@ -24,8 +24,6 @@ if (defaultState.featureFlagBraveNewsFeedV2Enabled) {
export type OnReadFeedItem = (args: TodayActions.ReadFeedItemPayload) => any
export type OnSetPublisherPref = (publisherId: string, enabled: boolean) => any
export type OnPromotedItemViewed = (args: TodayActions.PromotedItemViewedPayload) => any
export type Props = {
isFetching: boolean
hasInteracted: boolean
@@ -36,7 +34,6 @@ export type Props = {
articleToScrollTo?: BraveNews.FeedItemMetadata
displayedPageCount: number
onReadFeedItem: OnReadFeedItem
onPromotedItemViewed: OnPromotedItemViewed
onFeedItemViewedCountChanged: (feedItemsViewed: number) => any
onSetPublisherPref: OnSetPublisherPref
onAnotherPageNeeded: () => any
@@ -12,7 +12,7 @@ export default function useReadArticleClickHandler (action: OnReadFeedItem, payl
e.preventDefault()
const shouldOpenInNewTab = detectShouldOpenInNewTab(e)
action({ ...payloadData, openInNewTab: shouldOpenInNewTab })
}, [action, payloadData.item, payloadData.isPromoted])
}, [action, payloadData.item])
}
export function detectShouldOpenInNewTab (e: React.MouseEvent): boolean {
@@ -849,7 +849,6 @@ class NewTabPage extends React.Component<Props, State> {
}
onCustomizeBraveNews={() => { this.openSettings(SettingsTabType.BraveNews) }}
onReadFeedItem={this.props.actions.today.readFeedItem}
onPromotedItemViewed={this.props.actions.today.promotedItemViewed}
onSetPublisherPref={this.props.actions.today.setPublisherPref}
onCheckForUpdate={this.props.actions.today.checkForUpdate}
/>
@@ -19,13 +19,10 @@ import { BraveNewsControllerRemote } from '../../../../brave_news/browser/resour
// ]
//
// copy(newState.feed.pages[0].items.map( i => ({ ...i, items: i.items.map(ii => {
// let data = ii.article?.data || ii.promotedArticle?.data || ii.deal?.data
// if (ii.article) { data = ii.article.data }
// if (ii.promotedArticle) { data = ii.promotedArticle.data }
// data.publishTime.internalValue = data.publishTime.internalValue.toString()
// data.publisherId = pids[Math.floor(Math.random() * (pids.length - 1))]
// if (!ii.article) ii.article = "undefined"
// if (!ii.promotedArticle) ii.promotedArticle = "undefined"
// if (!ii.deal) ii.deal = "undefined"
// return ii
// })})))
@@ -352,42 +349,6 @@ export const feed: BraveNews.Feed = {
}
]
},
{
'cardType': 6,
'items': [
{
article: undefined,
deal: undefined,
promotedArticle: {
'data': {
'categoryName': 'Brave Partners',
'channels': ['Brave Partners'],
'publishTime': {
internalValue: BigInt('13278621628000000')
},
'title': 'Audiovox (VOXX) Q2 2022 Earnings Call Transcript',
'description': 'VOXX earnings call for the period ending September 30, 2021.',
'url': {
'url': 'https://www.fool.com/earnings/call-transcripts/2021/10/13/audiovox-voxx-q2-2022-earnings-call-transcript/?source=thebrave&utm_source=foo&utm_medium=feed&utm_campaign=article'
},
'urlHash': '',
'image': {
imageUrl: undefined,
paddedImageUrl: {
'url': 'https://pcdn.brave.com/brave-today/cache/5b3d8da219eee17bce800689085994a6a851545aa99b35c374874f42a93c672b.jpg.pad'
}
},
'publisherId': 'a5eece347713f329f156cd0204cf9b12629f1dc8f4ea3c1b67984cfbfd66cdca5',
'publisherName': 'The Motley Fool',
popScore: 0,
'score': 14.338672770645763,
'relativeTimeDescription': '23 minutes ago'
},
'creativeInstanceId': 'd2d506aa-5531-4069-8f85-7d9052f1b640'
}
}
]
},
{
'cardType': 2,
'items': [
@@ -1313,42 +1274,6 @@ export const feed: BraveNews.Feed = {
}
]
},
{
'cardType': 6,
'items': [
{
article: undefined,
deal: undefined,
promotedArticle: {
'data': {
'categoryName': 'Brave Partners',
'channels': ['Brave Partners'],
'publishTime': {
internalValue: BigInt('13278528021000000')
},
'title': 'The Beginners Guide to Account-Based Marketing (ABM)',
'description': 'This leads to a common paradox—marketing can hit its goals by bringing in a high volume of leads, but sales cant hit its goals because those same leads are poorly qualified. Account-based marketing (ABM) aims to fix that by tightly…Read more ',
'url': {
'url': 'https://ahrefs.com/blog/account-based-marketing/'
},
'urlHash': '',
'image': {
imageUrl: undefined,
paddedImageUrl: {
'url': 'https://pcdn.brave.com/brave-today/cache/9d2f7ab67a81520a1e281d9def23ce4f4fbcf46cac8be01707f0cd0fb24e597a.jpg.pad'
}
},
'publisherId': 'b4eece347713f329f156cd0204cf9b12629f1dc8f4ea3c1b67984cfbfd66cdca5',
'publisherName': 'Ahrefs',
popScore: 0,
'score': 22.921395422138495,
'relativeTimeDescription': '1 day ago'
},
'creativeInstanceId': '2626e169-a372-42ca-af14-b0df795d2819'
}
}
]
},
{
'cardType': 2,
'items': [
@@ -109,19 +109,7 @@ base::expected<mojom::FeedItemPtr, std::string> ParseFeedItem(
ui::TimeFormat::Length::LENGTH_LONG, relative_time_delta));
}
// Detect type
if (feed_item.content_type == "brave_partner") {
if (!feed_item.creative_instance_id ||
feed_item.creative_instance_id->empty()) {
return base::unexpected(
base::StrCat({"Promoted item has empty creative_instance_id. url=",
feed_item.url}));
}
auto item = mojom::PromotedArticle::New();
item->creative_instance_id = *feed_item.creative_instance_id;
item->data = std::move(metadata);
return mojom::FeedItem::NewPromotedArticle(std::move(item));
} else if (feed_item.content_type == "product") {
if (feed_item.content_type == "product") {
auto item = mojom::Deal::New();
if (feed_item.offers_category) {
item->offers_category = *feed_item.offers_category;
+19 -28
View File
@@ -46,14 +46,21 @@ using mojom::CardType;
// added in the meantime to fix the build error. Remove this attribute and
// provide a proper fix.
[[clang::no_destroy]] std::vector<CardType> g_page_content_order = {
CardType::HEADLINE, CardType::HEADLINE,
CardType::HEADLINE_PAIRED, CardType::PROMOTED_ARTICLE,
CardType::CATEGORY_GROUP, CardType::HEADLINE,
CardType::HEADLINE, CardType::HEADLINE_PAIRED,
CardType::HEADLINE_PAIRED, CardType::DISPLAY_AD,
CardType::HEADLINE, CardType::HEADLINE,
CardType::PUBLISHER_GROUP, CardType::HEADLINE_PAIRED,
CardType::HEADLINE, CardType::DEALS};
CardType::HEADLINE,
CardType::HEADLINE,
CardType::HEADLINE_PAIRED,
CardType::CATEGORY_GROUP,
CardType::HEADLINE,
CardType::HEADLINE,
CardType::HEADLINE_PAIRED,
CardType::HEADLINE_PAIRED,
CardType::DISPLAY_AD,
CardType::HEADLINE,
CardType::HEADLINE,
CardType::PUBLISHER_GROUP,
CardType::HEADLINE_PAIRED,
CardType::HEADLINE,
CardType::DEALS};
// TODO(https://github.com/brave/brave-browser/issues/48713): This is a case of
// `-Wexit-time-destructors` violation and `[[clang::no_destroy]]` has been
@@ -70,10 +77,6 @@ mojom::FeedItemPtr FromDeal(mojom::DealPtr deal) {
return mojom::FeedItem::NewDeal(std::move(deal));
}
mojom::FeedItemPtr FromPromotedArticle(mojom::PromotedArticlePtr item) {
return mojom::FeedItem::NewPromotedArticle(std::move(item));
}
bool MatchesDealsCategory(const std::string& category_name, mojom::Deal* deal) {
return (deal->offers_category == category_name);
}
@@ -142,7 +145,6 @@ void TakeRandom(
// could be 2 cards (e.g. HEADLINE_PAIRED) or multiple
// articles (e.g. CATEGORY_GROUP).
void BuildFeedPageItem(std::list<mojom::ArticlePtr>* articles,
std::list<mojom::PromotedArticlePtr>* promoted_articles,
std::list<mojom::DealPtr>* deals,
const std::string& deal_category_name,
const std::string& article_category_name,
@@ -235,8 +237,6 @@ void BuildFeedPageItem(std::list<mojom::ArticlePtr>* articles,
// closer to this item being viewed.
break;
case CardType::PROMOTED_ARTICLE:
Take<mojom::PromotedArticle>(1u, promoted_articles, &page_item->items,
base::BindRepeating(&FromPromotedArticle));
break;
}
}
@@ -323,7 +323,6 @@ bool BuildFeed(const std::vector<mojom::FeedItemPtr>& feed_items,
ChannelsController::GetChannelsFromPublishers(*publishers, subscriptions);
std::list<mojom::ArticlePtr> articles;
std::list<mojom::PromotedArticlePtr> promoted_articles;
std::list<mojom::DealPtr> deals;
std::hash<std::string> hasher;
absl::flat_hash_set<std::string> seen_articles;
@@ -378,21 +377,15 @@ bool BuildFeed(const std::vector<mojom::FeedItemPtr>& feed_items,
deals.push_back(std::move(item->get_deal()));
break;
case mojom::FeedItem::Tag::kPromotedArticle:
promoted_articles.push_back(std::move(item->get_promoted_article()));
break;
}
}
VLOG(1) << "Got articles # " << articles.size();
VLOG(1) << "Got deals # " << deals.size();
VLOG(1) << "Got promoted articles # " << promoted_articles.size();
// Sort by score, ascending
articles.sort([](mojom::ArticlePtr& a, mojom::ArticlePtr& b) {
return (a.get()->data->score < b.get()->data->score);
});
promoted_articles.sort(
[](mojom::PromotedArticlePtr& a, mojom::PromotedArticlePtr& b) {
return (a.get()->data->score < b.get()->data->score);
});
deals.sort([](mojom::DealPtr& a, mojom::DealPtr& b) {
return (a.get()->data->score < b.get()->data->score);
});
@@ -491,17 +484,15 @@ bool BuildFeed(const std::vector<mojom::FeedItemPtr>& feed_items,
for (auto card_type : g_page_content_order) {
auto feed_page_item = mojom::FeedPageItem::New();
feed_page_item->card_type = card_type;
BuildFeedPageItem(&articles, &promoted_articles, &deals,
deal_category_name, article_category_name, false,
&feed_page_item);
BuildFeedPageItem(&articles, &deals, deal_category_name,
article_category_name, false, &feed_page_item);
feed_page->items.push_back(std::move(feed_page_item));
}
for (auto card_type : g_random_content_order) {
auto feed_page_item = mojom::FeedPageItem::New();
feed_page_item->card_type = card_type;
BuildFeedPageItem(&articles, &promoted_articles, &deals,
deal_category_name, article_category_name, true,
&feed_page_item);
BuildFeedPageItem(&articles, &deals, deal_category_name,
article_category_name, true, &feed_page_item);
feed_page->items.push_back(std::move(feed_page_item));
}
feed->pages.push_back(std::move(feed_page));
@@ -384,7 +384,8 @@ TEST_F(BraveNewsFeedBuildingTest, DuplicateItemsAreNotIncluded) {
ASSERT_TRUE(BuildFeed(feed_items, history_hosts, &publisher_list, &feed,
pref_manager_->GetSubscriptions()));
ASSERT_EQ(feed.pages.size(), 1u);
ASSERT_EQ(feed.pages[0]->items.size(), 18u);
// 15 items in `g_page_content_order` + 2 in `g_random_content_order`.
ASSERT_EQ(feed.pages[0]->items.size(), 17u);
}
} // namespace brave_news
@@ -88,8 +88,6 @@ std::tuple<FeedItems, ETags> FeedFetcher::CombineFeedSourceResults(
GURL url;
if (item->is_article()) {
url = item->get_article()->data->url;
} else if (item->is_promoted_article()) {
url = item->get_promoted_article()->data->url;
}
// Skip this, we've already seen it.