Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Facebook Ad Content Evades Aggressive Mode #15869

Closed
jonathansampson opened this issue May 14, 2021 · 14 comments
Closed

Facebook Ad Content Evades Aggressive Mode #15869

jonathansampson opened this issue May 14, 2021 · 14 comments
Labels
closed/duplicate Issue has already been reported feature/shields/adblock Blocking ads & trackers with Shields OS/Desktop uBO-parity

Comments

@jonathansampson
Copy link
Contributor

Description

Facebook displays "Sponsored" items on the main feed and in the side-bar, even when shields are configured to engage in Aggressive blocking.

Steps to Reproduce

Use Facebook, and look at the items in your feed and side-bar 🙂

Actual result:

Items in the top-right:

image

Items in the feed:

image

Expected result:

Ads should not be shown when the user has engaged Aggressive blocking.

Reproduces how often:

Easily.

Brave version (brave://version info)

1.24.85

Miscellaneous Information:

The DOM around Sponsored content is designed to confuse queries. For example, the "Sponsored" string is constructed for numerous elements, interspersed with positioned elements throughout.

sponsored-2

One approach would be to use a Mutation Observer to identify/excise ads. The following was constructed as a quick proof of concept. A clear limitation here would be the expectation that the string displayed to the user is in English. This is not likely to be the case for most users.

// We expect the feed element to exist before proceeding
document.addEventListener( "DOMContentLoaded", () => {

    // Hides a post if it has <span>o</span> or <span>Suggested for You</span>
    // Posts have numerous spans, with 'Sponsored' broken up into chars
    const removeSponsoredFeedItems = post => {
        const nodes = post.querySelectorAll( "span:not([style])" ) || [];
        const spans = [ ...nodes ];
        spans.some( e =>
            e.textContent === "o"||
            e.textContent === "Suggested for You"
        ) && post.remove();
    };

    // Listens for new post elements in the feed
    const observer = new MutationObserver( changes => {
        for ( const change of changes )
            for ( const node of change.addedNodes )
                if ( node.parentElement.matches("[role='feed']") )
                    removeSponsoredFeedItems( node );
    });
    
    const feed = document.querySelector("[role='feed']");
    const nodes = feed.querySelectorAll("[data-pagelet^='FeedUnit']");
    const config = { childList: true, subtree: true };

    // Processes first sponsored element in the page
    nodes.forEach( removeSponsoredFeedItems );
    
    // Processes any subsequent sponsored element in the page
    observer.observe( document.documentElement, config );

});
@jonathansampson jonathansampson added feature/shields/adblock Blocking ads & trackers with Shields OS/Desktop labels May 14, 2021
@rebron
Copy link
Collaborator

rebron commented May 14, 2021

cc: @antonok-edm @ryanbr

@jonathansampson
Copy link
Contributor Author

Playing with this a bit more; the approach I shared in the top post is not sufficient. Facebook does the same DOM-goofiness with more than just 'Sponsored' strings. They do it with other strings like 'Just Now' for recent posts, and more.

Another approach is to look for a suspicious span, filter-out some of its siblings, and then map their textContent values to a string that can be compared against a list of "Sponsored" translations. This approach appears to work fairly well; I've tested in both English and Brazilian Portuguese. I'm also watching the amount of mutations that occur routinely, and how long it takes to process each batch. It's very quick (almost always less than 1ms).

const translations = ["sponsored", "patrocinado"];

const removeIfSponsored = node => {
    const floaty = node.querySelector("[style='position: absolute; top: 3em;']:not([brave-processed])");

    if (floaty) {
        floaty.setAttribute("brave-processed", true);

        const message = [...floaty.parentElement.children]
            .filter(x => !x.hasAttribute("style"))
            .map(x => x.textContent).join('');

        if (translations.includes(message.toLowerCase())) {
            floaty.closest("[role='article']").style.transform = "rotate(180deg)";
        }
    }
};

// Listens for new post elements in the feed
const observer = new MutationObserver(changes => {
    const start = Date.now();
    for (const change of changes) {
        for (const node of change.addedNodes) {
            if (node.nodeType === 1) {
                removeIfSponsored(node);
            }
        }
    }
    const stop = Date.now();
    console.log(`Reviewed ${changes.length} mutations in ${stop - start}ms`);
});


observer.observe(document.documentElement, {
    subtree: true, childList: true
});

@antonok-edm
Copy link
Collaborator

I believe the relevant filters are already in uBO's default lists, but they require procedural filtering - @ryanbr does that sound right?

It would be ideal to get procedural filters implemented; then we can also benefit from upstream updates (the sites where procedural filtering is necessary tend to be the trickiest ones to work against). Should also work on (I think) Quora and Twitter, and do a little bit of cleanup on YouTube.

cc @pes10k

@jonathansampson
Copy link
Contributor Author

Attaining parity with uBO (or enough that we can use many of their procedural filtering rules) would be excellent. In the meantime, however, we could likely deliver a shim via Greaselion to clean up Twitter, YouTube, Facebook, and a small handful of commonly-visited sites.

@ryanbr
Copy link

ryanbr commented May 15, 2021

Even with uBO, I'm still seeing ads in Facebook, It doesn't seem they're filtering facebook ads currently @antonok-edm

@Tonev
Copy link
Contributor

Tonev commented May 15, 2021

@jonathansampson

As long as it concerns uBlock Origin and those sponsored ads in the feed, the following uBlock Origin filters work flawlessly for me so far: https://www.reddit.com/r/uBlockOrigin/wiki/solutions#wiki_facebook

For the ads in the top right, I believe I used uBlock Origin's Element Picker to get rid of them.

@pes10k
Copy link
Contributor

pes10k commented May 15, 2021

re @antonok-edm I think adding support for procedural filters is a great idea, but a wrinkle to consider is that we'd need to have an implementation that was compatible with our 1p/3p checks in the default consideration. Or, alternatively, we could just not apply procedural filters by default, and only apply them in aggressive mode

@antonok-edm
Copy link
Collaborator

@pes10k I think procedural filtering would definitely be appropriate as an aggressive-only feature, given that its use-case is hiding inline-content-styled sponsored material.

@jonathansampson
Copy link
Contributor Author

jonathansampson commented May 15, 2021

I packaged-up a proof of concept into an extension: https://github.com/jonathansampson/extension-facebook-sponsored-content-blocker

@illtellyoulater
Copy link

@jonathansampson I'm testing your extension and I'm using aggressive mode, but facebooks ads are still going through...
Can you confirm version 0.0.0.1 no longer works?

@illtellyoulater
Copy link

illtellyoulater commented Nov 19, 2021

Not sure if this can help with the code but I found about https://github.com/facebook-adblock/facebook_adblock, an ad-blocker specific for facebook which seems to work pretty good.

@jonathansampson
Copy link
Contributor Author

@illtellyoulater My prototype is relatively quite dated, so I'm not surprised it was failing at this point 🙂

@illtellyoulater
Copy link

@jonathansampson I saw you kept working on it during last year (till Sept 2022), have you tried it recently to see if it's currently working?

@antonok-edm
Copy link
Collaborator

Procedural filtering has been released as of Brave 1.72.18 (currently Nightly channel). This should be fixed there - if not, please let me know!

@antonok-edm antonok-edm added the closed/duplicate Issue has already been reported label Sep 18, 2024
@antonok-edm antonok-edm added this to the Closed / Dupe / Invalid milestone Sep 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed/duplicate Issue has already been reported feature/shields/adblock Blocking ads & trackers with Shields OS/Desktop uBO-parity
Projects
None yet
Development

No branches or pull requests

7 participants