F.B. Sponsored/Ad Post Blocker version history - 25 versions
F.B. Sponsored/Ad Post Blocker by Inky
Be careful with old versions! These versions are displayed for testing and reference purposes.You should always use the latest version of an add-on.
Latest version
Version 1.1.95
Released Oct 28, 2026 - 118.72 KBWorks with firefox 109.0 and later, android 120.0 and later- 1.1.94 could hide the whole News Feed behind one "Sponsored post hidden"
bar. Its new check for "Ad" labels that arrive late could fire while
Facebook was still laying the page out, when nothing on it had a size yet.
The search for the rest of the post measures sizes to know where a post
ends, so with everything at zero it kept going until it reached the whole
feed column, stopping at the "What's on your mind?" box. It now stops
where it is when nothing has a size, and the feed column itself can never
be hidden as a post.
92 automated tests pass. The new one reproduces the hidden feed on 1.1.94 —
an "Ad" label connected while the feed column has no size — and passes on
this build.Source code released under MIT License
- 1.1.94 could hide the whole News Feed behind one "Sponsored post hidden"
Older versions
Version 1.1.94
Released Oct 27, 2026 - 118.22 KBWorks with firefox 109.0 and later, android 120.0 and later- An ad from a Page your friends follow was credited to a friend. Such an
ad opens with "Your friend and another friend follow …", and the extension
took the first person linked in a post to be the poster. So the "Show"
placeholder showed your friend's name instead of the advertiser's — and,
worse, "Not an ad" put your friend on "Always show these Pages", and "This
is an ad" put your friend on "Always treat these Pages as ads", after which
their own posts linking off Facebook could be hidden. The poster is now the
one named in the post's heading. If you used either button on such an ad,
check "Your lists" in the popup for a friend's name and remove it. - Some ads labelled "Ad" stayed in the feed. Facebook sometimes connects
a post to its "Ad" label seconds after the post appears — once, 20 seconds
later. The extension only looked when the post first arrived, so unless
another clue gave the ad away, it stayed. It now notices the label being
connected. In a 14-minute scroll of a live feed, 3 of about 90 ads were
missed this way.
91 automated tests pass, five of them new. Three cover the placeholder's name
and what each button records on an ad that opens with a friends line. Two
cover a label connected late: the ad is hidden, and an ordinary post whose
timestamp arrives the same way is not. The four ad tests fail on 1.1.93. On a
live feed, 32 hidden posts were checked: two had been credited to a friend and
now name the advertiser; the rest are unchanged.Source code released under MIT License
- An ad from a Page your friends follow was credited to a friend. Such an
Version 1.1.93
Released Oct 26, 2026 - 117.13 KBWorks with firefox 109.0 and later, android 120.0 and later- A safety net under every rule. Whichever rule finds an ad, the extension
now refuses to hide anything that is, or holds, the page's main area, feed
or navigation, or that is wider than a feed post on a computer. If the part
it settled on is too big, it hides the post-sized card inside it around the
label, or nothing. Both page-wide bugs so far — the right-hand column and
Marketplace categories — would have been stopped by this alone. Suggested by
a contributor in a fork of the project. Diagnostics counts what it refused.
- Spanish, Portuguese, French, German, Indonesian, Arabic and Hungarian.
The ad label in each language, read off a live feed with Facebook set to it,
now counts — but only where Facebook puts the label, after the poster's
name, so post text or a Page's name never does. Their Follow/Join buttons, Stories, Reels
and People you may know headings, and how they write a post's age now work
too; the last keeps real posts that share a link from being taken for ads.
English is unchanged, except that its label is now read as "Sponsored ·" as
well, the way Facebook writes it on some ads.
86 automated tests pass, thirteen of them new: neither a wide page column nor
the main page area is ever hidden, labels in English and other languages, a
foreign ad word in post text, a Page name or Facebook's Hungarian footer link
ignored, Hungarian's Follow button, and posts kept by their age in Spanish,
Arabic and Hungarian. On a live English feed afterwards, ads were hidden as
before and nothing wider than a post was. The test page no longer depends on
being on screen to run scheduled scans.Source code released under MIT License
- A safety net under every rule. Whichever rule finds an ad, the extension
Version 1.1.92
Released Oct 20, 2026 - 113.66 KBWorks with firefox 109.0 and later, android 120.0 and later- Marketplace categories showed nothing. Opening a category such as
Vehicles left a single "Sponsored post hidden" bar where the listings should
be. The results include sponsored tiles, and to hide one the extension
looked for the post around it the way it does in the feed — outward until
the page column — which on Marketplace is the whole grid of results. On
Marketplace a sponsored tile is now hidden on its own, and the listings
around it stay. The rules meant for feed posts (suggested posts, Pages you
don't follow, ads Facebook doesn't label, feed sections) no longer run on
Marketplace at all. Listings you open are left alone, as before.
73 automated tests pass, two of them new: a sponsored tile hidden alone, and
the listings beside it kept. The second fails on 1.1.91 exactly as reported.
On a live Vehicles search, each sponsored tile resolved to that one tile and
nothing else.Source code released under MIT License
- Marketplace categories showed nothing. Opening a category such as
Version 1.1.91
Released Oct 18, 2026 - 112.84 KBWorks with firefox 109.0 and later, android 120.0 and later- Hidden-post placeholders say who the post is from. With "Display a
'Show' button to reveal hidden posts" on, each placeholder now shows the
name of the person, Page or Group under the reason it was hidden, so you
can tell at a glance whether it should have been hidden without clicking
Show. Group posts show the group and the member who posted. Long names are
shortened, with the full name shown when you point at it. If Facebook has
not filled in the name yet when the post is hidden, the placeholder looks
again a moment later.
- Some posts were hidden twice. Two rules could settle on different parts
of the same post, so it counted twice towards "posts hidden", and a second
placeholder sat inside it — clicking Show revealed the post with part of it
still hidden behind another "post hidden" bar. Each post is now hidden once.
71 automated tests pass, four of them new, and all four fail on 1.1.90: a
Page's name read from its post heading rather than the "active" label on its
profile picture, a group post naming both the group and the member, and one
hide per post whichever part was hidden first. On a live feed every hidden
post was named correctly.Source code released under MIT License
- Hidden-post placeholders say who the post is from. With "Display a
Version 1.1.90
Released Oct 17, 2026 - 110.91 KBWorks with firefox 109.0 and later, android 120.0 and later- "This is NOT an ad". With the "This is an ad" buttons switched on, a
second button now sits beside it over a post's picture. Clicking it puts the
post's Page on "Always show these Pages", and from then on nothing hides that
Page's posts no rule, not even a "Sponsored" label after reloads and on
later visits too. The post you clicked is left alone straight away, even when
its Page can't be worked out.
- The two buttons now correct each other. "This is NOT an ad" takes the Page
off "Always treat these Pages as ads", and "This is an ad" takes it off
"Always show these Pages". Before, a Page could end up on both lists, where
"always show" quietly won and the later click seemed to do nothing. - The buttons over pictures and the "Show" placeholders now match Facebook's
own light or dark mode, whichever you have chosen there. In dark mode they
were pale grey and hard to read.
- On posts with several photos, the buttons appeared over the poster's name
instead of the picture. Facebook draws those photos larger than their tile
and crops the rest; the buttons now go by the part you can see.
67 automated tests pass, four of them new, and each fails without its change:
a Page marked "NOT an ad" stays visible after a reload even when its post is
labelled Sponsored, each button takes the Page off the other list, and the
buttons sit on the visible part of a cropped photo.Source code released under MIT License
- "This is NOT an ad". With the "This is an ad" buttons switched on, a
Version 1.1.89
Released Sep 30, 2026 - 109.11 KBWorks with firefox 109.0 and later, android 120.0 and later- Switches for Stories, Reels and People you may know. A new "Feed
sections" group in the popup can hide any of them: - Stories — the row of stories at the top of the feed. The frame around
it goes too, so no gap is left behind. - Reels — the Reels carousel in the feed.
- People you may know — the friend-suggestions block in the feed.
All three are off unless you turn them on, since none of them is an ad.
Reels and People you may know are only hidden when the block really holds
reels or friend suggestions, so a post or Page that merely has "Reels" in
its title is left alone. A hidden section stays hidden when Facebook redraws
it, and switching one back off brings it straight back.
For now these work on a computer only; Facebook's phone site is built
differently.
63 automated tests pass, seven of them new: each section hidden when its
switch is on and kept when it is off, a Page called "Reels" left alone, and a
hidden section surviving a redraw. On a live feed, each rule picked out
exactly its section and nothing else. The test page also runs correctly now
when it is not on screen.Source code released under MIT License
- Switches for Stories, Reels and People you may know. A new "Feed
Version 1.1.88
Released Sep 26, 2026 - 106.94 KBWorks with firefox 109.0 and later, android 120.0 and later- The right-hand column no longer disappears with its ad. With "Hide
Sponsored posts" on, the whole column went — Birthdays, Contacts and Group
chats along with the sponsored block at its top. Facebook now wraps every
section of that column in one element, which the extension took for the ad.
It now stops at the next section's heading, so only the sponsored block goes. - "Show" on a hidden post now lasts. The next scan used to hide it again a
moment later. A post you reveal stays revealed while it is on the page. - Every placeholder can keep its page. "Not an ad" (on sponsored posts) or
"Always show" (on the rest) adds the page to your keep list, so its posts
stay visible after a reload too. This used to be offered only for ads
Facebook doesn't label. The keep list now applies to every rule, matched on
who posted: an ad that merely tags a page on the list is still hidden. - An ad could come back and stay. Hidden posts are re-checked when they
change, and if an ad's label was missing at that moment — a video player
reloading, say — the ad was shown again and never looked at twice. It is now
checked again one and three seconds later. - Ads hidden without a label (unlabelled ads, or ones you marked with "This is
an ad") could flash back for up to half a second whenever something inside
them changed. They now stay hidden.
- A simpler popup. Settings are switches in two groups, "What to hide" and
"While browsing", each with a short name and one plain line on what it does.
The two page lists are folded into "Your lists", which shows how many pages
they hold; the buttons on the page fill them in, so most people never need
to open it. Follows the browser's dark mode. Every setting is unchanged.
56 automated tests pass, seven of them new. Six fail on 1.1.87, so each
catches the bug it was written for; the seventh checks that an ad tagging a
kept page is still hidden.Source code released under MIT License
- The right-hand column no longer disappears with its ad. With "Hide
Version 1.1.87
Released Sep 25, 2026 - 103.9 KBWorks with firefox 109.0 and later, android 120.0 and laterCleanup. What the extension hides, and how, is unchanged.
- Hidden posts that leave the page are no longer kept in memory. Every
hidden post was remembered so it could be put back, and only putting it back
ever forgot it — so a hidden post that Facebook removed from the page stayed
in memory, whole, until the tab was closed. Those entries are now dropped
every few seconds, with the hide undone on the way out: Facebook reuses page
elements, and one that came back still hidden but no longer tracked could
never have been restored. The Diagnostics panel shows how many hidden posts
are remembered and how many were let go.
- The code's comments explain the code rather than its history. Dates,
version numbers and what a panel showed on a given day belong in this file,
and seventy comment blocks retold them. They now say why each part works the
way it does, and keep every warning about what breaks if it changes. Two
were simply wrong and are corrected. Comments no longer name advertisers or
Pages. Only comment lines changed; every other line is identical. - A lookup written out five times is one shared helper; the diagnostics use the
shared link parser instead of their own copy; three values nothing used are
gone. - A shortcut the test harness uses to pretend it is on a different page is now
ignored entirely in a real install.
On a mock feed of fifty posts and about 22,000 page elements, the twice-a-second
check takes about 1.4 ms, and the "This is an ad" hover handler 0.004–0.065 ms
per mouse movement. Neither is worth changing. Earlier, higher figures had timed
cold runs and the cost of the simulated mouse events, not the extension.
49 fixtures pass. The two new behaviors were checked directly: a hidden post
removed from the page is let go, its hide undone, and hidden again if it comes
back; the page-address shortcut works in the harness and is ignored in a real
build.Source code released under MIT License
- Hidden posts that leave the page are no longer kept in memory. Every
Version 1.1.86
Released Sep 23, 2026 - 110.4 KBWorks with firefox 109.0 and later, android 120.0 and laterTwo reports on 2026-09-23, from the same kind of ad.
- "This is an ad" hid the picture and left the post.
So the climb is anchored on something every post has instead: the line
saying who posted it. A hide is not finished until it contains that. The
climb keeps every guard that keeps it inside one post — it never crosses
into the page column, a viewer, above a hidden neighbour, or into a container
that holds another post — and if it cannot find a byline inside those
bounds it leaves the hide exactly as it was. It can widen a hide within a
post; it cannot widen one into the feed.
The page name is now read from the whole post as well, which is why the list
stayed empty: a picture's only links are/photo/and the advertiser's own
site, neither of which is a page.- Lead-form ads got through entirely. Hill's Pet Nutrition, "FORM — Enter
for a chance to win — Sign up". That button opens a form on Facebook itself,
so the ad never links off it; its "Ad" label is the unreadable kind; and it
carries no dangling byline reference. Every route in missed it.
What every ad does carry is a call to action, so that is now a third way in:
"Sign up", "Apply now", "Get quote", "Shop now" and the rest of the words
Facebook puts on those buttons. It only makes a card a candidate — every veto
that protects a real post still applies afterwards. "Message", "Send
message", "Join", "Follow" and "Interested" are deliberately not on the list:
every marketplace listing and group post carries one of those.
- "Hidden" now means the whole post is gone. The runner counted a post as
hidden if a hide marker existed anywhere inside it — so a post with only
its picture hidden passed. That is precisely the bug that reached the screen
four times, and not one fixture could see it. There are now three outcomes:
hidden, visible, and partial, which is never right. - A fixture can press "This is an ad", the way a person does — point at the
picture, click the button — and can check which page name was recorded.
- The Diagnostics panel reports
half-hidden posts, and keeps the latest
eight hides as samples rather than the first eight. The Renaissance half-hide
was the seventeenth of its page, so the old panel could never have shown it.
49 fixtures, twenty-three of them false-positive guards. Both new bug fixtures
were confirmed to fail on 1.1.85 first — and the first draft of the
Renaissance one did not: it gave the ad an off-Facebook link, so a second pass
of the shape rule rescued the post and hid the lot. That is exactly what never
happened on the real page, where nothing links off Facebook. Redrawn as a
lead-form ad, with the click as the only thing that can hide it, it reproduces
the report:partialon 1.1.85, hidden — and 'ads` recorded
— on 1.1.86.Source code released under MIT License
Version 1.1.76
Released Sep 11, 2026 - 92.96 KBWorks with firefox 109.0 and later, android 120.0 and laterAds still losing their picture and keeping everything else — Wayfair by
screenshot, and Great Rail Journeys straight from the panel, which listed the
same card underHIDDEN BY SHAPEand underNOT HIDDEN. 1.1.75 fixed the
wrong half of this.
- The climb ran out of steps. It was capped at fourteen levels, and
Facebook nests a call-to-action block twenty-odd elements below the card, so
a climb starting at an outbound link never reached the card and kept whatever
fitted on the way up — the media block. Raised to thirty.
No fixture caught this because every fixture card was four levels deep. There
is one thirty levels deep now, and it failed before this change.- Nothing enforced "hide whole cards". Whatever route picks an element, if
it is feed-post width the thing to hide is the card, so the hide now walks up
until the next step would leave it. - That walk could have taken the entire feed. It stops when a parent holds
more than one card-shaped child — but a post that is already hidden has no
box, so a feed column with everything else hidden looks exactly like a single
card. On a short feed it would have hidden the lot. It now asks whether the
siblings are posts rather than whether they are visible. Found by the
harness, before it ever ran on a page.
- Fixtures now sit beside a neighboring post. The sixteen nesting wrappers
were indistinguishable from a card's own — each holding one child of the same
width — so the expand-to-card walk climbed out of the fixture and into the
harness itself. On a real page those wrappers belong to the card; here they
do not, and nothing said so. - The panel reports which rule made each hide (
via label/via shape).
Both report "sponsored", so a partial hide looked identical whichever
produced it, and telling them apart took a screenshot and a guess.
Source code released under MIT License
- The climb ran out of steps. It was capped at fourteen levels, and
Version 1.1.65
Released Sep 11, 2026 - 78.62 KBWorks with firefox 109.0 and later, android 120.0 and laterDESKTOP-AD-LABELS.md— a record of every mechanism tried against ads
whose label cannot be read from the DOM, and how each was ruled out. Eleven of
them: leaf text, non-leaf own text, SVG<text>,<use>sprites, live
aria-labelledby, rescue-from-removal, adocumentElement-rooted observer,
the/ads/about/path, CSS::before/::aftercontent,background-image
andmask-image.
Written because the answer to "have we tried X?" was becoming expensive. The
next attempt should start from what is already eliminated.data-ad-rendering-roleis still not an ad marker, despite being all over
these cards and looking ideal when nothing else was left. Measured on a live
feed: a local buy-sell group post — "Oakland County, MI Sell…" — reportedad-roles=profile_name,story_message,meta,title ad-preview=yes ads/about=no
Not an ad.data-ad-previewanddata-ad-comet-previeware no better; the
same post carried those too. The long-standing warning inclassifyLabelnow
carries a 2026 date and this counter-example, in the code and in the doc.
It does not hide ads whose label is absent from the page text — the Dyson and
Power Crunch formats.textContenton a Dyson card readsDyson Verified account · Shared with Public…: the icon titles are present
and the word "Ad" simply is not. That is not obfuscation to see through; there
is nothing there.
The remaining option is a shape heuristic — advertiser domain, call-to-action,
and a byline anchor with no path — left unimplemented because it would also
match a friend sharing a news article. The fixture corpus and itsexpect: visibleguards exist to make that decision measurable if it is ever
taken.Source code released under MIT License
Version 1.1.56
Released Sep 11, 2026 - 70.89 KBWorks with firefox 109.0 and later, android 120.0 and later- The unhidden-card report now shows how a label could be reached, not just
what text is present. Two ad cards in a row reported this shape:
#2 680x837 cls=x1lliihq
span:"fairlife" span:"·" a:"#fairlifepartner" span:"FAIRLIFE.COM"
Note thespan:"·"— the separator that sits after "Ad" in Facebook's
byline — with no "Ad" text anywhere on the card. Different advertisers, same
absence, so on these cards the label is not rendered text and the panel had
no way to say what it actually is.
Each card now also reports the three remaining mechanisms by which "Ad" could
be carried:<use>sprite references and what they resolve to,
aria-labelledbytargets (falling back to the label cache when Facebook has
already deleted the span), and plainaria-labelvalues. Example from a
harness card with no "Ad" text at all:evidence: by#lbl1->"Ad" aria:"Sponsored content"
Whichever line carries "Ad" is what detection has to read.
Because every fix since 1.1.51 was chosen from a console probe, and probes race
a DOM that deleted 125 of 128 label nodes in 40 seconds. Three of them looked
right and none landed. This reports from inside the content script, against the
same document the scan sees, and it costs nothing until the panel is opened.Source code released under MIT License
- The unhidden-card report now shows how a label could be reached, not just
Version 1.1.55
Released Sep 11, 2026 - 69.89 KBWorks with firefox 109.0 and later, android 120.0 and later- The
VISIBLE, NOT HIDDENreport was unreadable. Its first live run came
back as ten identicalspan:"Facebook"entries per card. Those are icon
<title>elements — leaves with text, no box, dozens per card — and taking
the first ten in DOM order crowded out every label that mattered.
It now reports only leaves that actually render, each text once. The same
card in a harness goes from tenspan:"Facebook"to
span:"Verkada" span:"Ad" span:"See more".- Landmarks were read off the wrong element. The report used
getAttributeon the card's outermostdiv, so every card looked
landmark-less — including ones the extension anchors without trouble. It now
usesclosest()andquerySelector(), and says whether the landmark is on
the card or one of its descendants.
This matters for the open question: the live report showed 680x781 ad cards
with no landmarks, while posts of the same width were being hidden
successfully. That contradiction was an artefact of where the attribute was
read, not a fact about the page.Source code released under MIT License
- The
Version 1.1.54
Released Sep 11, 2026 - 69.19 KBWorks with firefox 109.0 and later, android 120.0 and later- The diagnostics panel now reports what it is missing. A
VISIBLE, NOTsection lists the post-sized cards on screen that were not hidden,
HIDDEN
with each card's dimensions, landmark attributes, and the short texts found
inside it:
VISIBLE, NOT HIDDEN:
#1 680x783 role=- posinset=- pagelet=-
span:"Verkada" span:"Ad" span:"See more" span:"Like"
Three releases in a row were aimed at ads that stayed visible, each built on
a plausible reading of a console probe, and none of them landed. The reason
is in the numbers from an earlier run: of 128 label nodes recorded over 40
seconds, 125 were deleted by Facebook before they could be examined
again. EveryquerySelectorAllprobe sampled an instant of that, and five
consecutive probes gave five different answers.
Reporting from inside the content script is not subject to that race. It sees
the same document the scan sees, and it says what the label element actually
is — whatever Facebook wraps it in this week — instead of requiring a guess.
Computed only when the panel is opened, so it costs nothing during browsing.matched 10, anchored 10on 1.1.53 means everything recognised is being
hidden, so the ads getting through are not being recognised at all. Nothing in
the panel could say why, because it only described what the extension did.
This closes that gap before any further attempt at the cause.Source code released under MIT License
- The diagnostics panel now reports what it is missing. A
Version 1.1.53
Released Sep 11, 2026 - 67.8 KBWorks with firefox 109.0 and later, android 120.0 and later- Ads whose label is drawn as SVG text were never examined. Facebook renders
some feed-ad labels as vector text rather than a<span>— an inline<svg>
holding<text>Ad</text>. Inspecting one on a live feed said it exactly:
classifies as Ad: true selectable: falseclassifyLabelrecognises it fine; its leaf branch readstextContentlike
any other element. ButLABEL_SELECTORlistedspan, a, use, [aria-label],and not
[aria-labelledby]text, so the scan never handed it over. Those ads
were never looked at, whoever the advertiser was.textis now in the selector. SVG text nodes are rare next to spans, and the
selector runs once per scanned subtree rather than per element, so the cost is
negligible.
Verified before and after against a harness with an ad card labelled by
<text>Ad</text>at realistic depth: 1.1.52 leaves it visible, 1.1.53 hides
it.
Whether those ads can then be anchored. A viewport sweep found the visible
ad card carried norole="article", noaria-posinsetand nodata-pagelet,
sofindPostContainermay still have nothing to grab even now that the label is
seen.
The diagnostics panel answers that without further probing: ifmatchednow
climbs aboveanchored, detection is fixed and anchoring is the remaining
problem, and the fix is a card-sized-ancestor climb of the kindclimbToChildOfalready does for the sidebar rail.Source code released under MIT License
- Ads whose label is drawn as SVG text were never examined. Facebook renders
Version 1.1.52
Released Sep 11, 2026 - 66.79 KBWorks with firefox 109.0 and later, android 120.0 and later- Ads whose label arrives in two steps were never hidden. Facebook inserts
the label span empty and fills it a moment later:
html
<span id="_r_7bt_"></span> <!-- inserted -->
<span id="_r_7bt_">Ad</span> <!-- text arrives separately -->rememberLabelTargetreads""on insertion and bails, and the text that
follows is a text node — which the observer'saddedNodesloop skipped
outright. The label was therefore never cached and the post never
re-examined, leaving the ad on screen with everything needed to hide it
present in the DOM.
Confirmed on a live desktop feed: a visible 781px sponsored post, its label
span present reading"Ad", its referrer inside anaria-posinsetand not
in a dialog — every condition for resolution satisfied, and not hidden.
Text nodes now complete their parent label instead of being dropped. Kept to
O(1): only a parent carrying anidand holding no element children can be
one of these spans, so it is atextContentread on a leaf plus aMapset,
not a subtree walk.rememberLabelTargetresolves forward from there, so no
extra scan is scheduled.
Verified before and after against a harness reproducing the two-step
insertion at realistic depth: 1.1.51 leaves the post visible, 1.1.52 hides it.- The diagnostics panel reports
late-text labels— how many labels completed
this way. Non-zero confirms the path is live; zero on a feed with ads means
they are being built some other way.
This is why the DOM probes disagreed with each other all session. Of 128 labels
recorded over 40 seconds on a live feed, 125 were removed by Facebook before
they could be examined again. Sampling withquerySelectorAllcatches an
instant of that; only the observer sees the transitions.Source code released under MIT License
- Ads whose label arrives in two steps were never hidden. Facebook inserts
Version 1.1.51
Released Aug 26, 2026 - 65.65 KBWorks with firefox 109.0 and later, android 120.0 and later- The extension silently did nothing on desktop.
document.bodywas null
when the content script ran, so this line threw:
TypeError: MutationObserver.observe: Argument 1 is not an object
content.js:1368
Module evaluation stopped there. The observer never attached, the initial
scan never ran, andmain()never executed — but the message listener
registers earlier in the file, so the popup kept answering and the extension
looked completely healthy. Settings on, permission granted, no visible error,
four sponsored posts sitting in the feed untouched.run_at: "document_idle"is supposed to guarantee a body exists. On
facebook.com in Firefox it does not, at least sometimes. The startup now
waits for` to appear instead of assuming it, watchingdocument.documentElement` until it does.
Verified before and after against a harness withdocument.bodyforced to
null: the previous build throws, this one starts clean and completes once a
body appears.- The diagnostics panel reports a
BOOT:line whenever startup has not
completed. This failure was invisible from the page and from the Browser
Console — it only surfaced in the page console — and the panel showing
"0 scans, 0 observer calls" after 147 seconds on a live feed is what led to
it. Now it says so directly.
Nothing was wrong with detection. Facebook had not changed anything: the
labels,aria-labelledbyportal spans andaria-posinsetanchors were all
present and would have resolved normally.Source code released under MIT License
- The extension silently did nothing on desktop.
Version 1.1.50
Released Aug 16, 2026 - 64.48 KBWorks with firefox 109.0 and later, android 120.0 and later- The mobile feed stopped paging because of a single attribute write.
1.1.49 preserved every post's height and the feed still stalled, so the
problem was never the geometry.
Isolated by hand on a live phone feed, with the extension installed but all
hiding switched off:
| What was done to 6 feed children | Feed after 15s |
| --- | --- |
| Nothing (extension inert) | +58 posts — paging normally |
|data-fbsb-hidden+ inline style | stalled |
|data-fbsb-hiddenonly, no styling | stalled |
|visibility: hiddenon their children | +34 posts — still paging |
Setting one data attribute, changing nothing visible, was enough to stop
Facebook's pager for the full window. Its own observers evidently treat a
direct feed child that has been written to as changed underneath it, and stop
reconciling.
So the post element is now untouchable — no style, no attribute. Both go on
its children instead: every element child getsvisibility: hidden, and the
marker attribute rides on the first of them. The children keep their boxes,
so the post keeps its height, and Facebook sees a feed it still owns.hiddenPostsis still keyed by the post, so recycled-node detection resolves
a marker to its parent when the marker is not itself a key.
Desktop is unchanged: it removes posts withdisplay: noneas before, and
nothing there is virtualised or reconciled this way.
That the feed keeps paging with the extension actually doing the hiding, over a
long scroll — the table above was measured by hand, six posts at a time. It
also leaves blank space where each hidden post was, which is now the only known
remaining problem on mobile rather than one of several.Source code released under MIT License
- The mobile feed stopped paging because of a single attribute write.
Version 1.1.49
Released Aug 16, 2026 - 63.29 KBWorks with firefox 109.0 and later, android 120.0 and later- The mobile feed stopped loading after about 15 seconds of scrolling, and
it was us. Established by control test on stock Firefox for Android:
extension disabled, the feed pages in new posts indefinitely; extension
enabled, it stalls. Same account, same feed, same browser.
Facebook virtualises that feed and decides what to page in next by measuring
rendered content.display: noneremoves a post's height from that
measurement, so hiding posts corrupts the figure the loop works from — hide
enough and it stops paging entirely. This also explains two things that never
quite added up: unchecking "Hide posts from Pages/Groups you don't follow",
which hides the largest share of any feed, made the blackout go away; and
1.1.44's pre-hiding, which hid far more posts than anything before it, made
it dramatically worse.
On mobile a post is now hidden withvisibility: hiddeninstead. It becomes
invisible while its box keeps exactly the height it had, so Facebook's
accounting sees a feed that never changed shape.
The cost is deliberate and visible: a hidden ad leaves blank space where it
was, rather than vanishing. That is the same gap reported throughout
testing, now accepted on purpose — blank space you can scroll past beats
content you cannot reach. Desktop is unchanged and still removes posts
outright; nothing there is virtualised this way.
With placeholder mode on, mobile now shows both the placeholder bar and the
blank space of the post behind it. Untidy, and left alone for now: the point of
this release is to find out whether preserving height keeps the feed alive.Source code released under MIT License
- The mobile feed stopped loading after about 15 seconds of scrolling, and
Version 1.1.48
Released Aug 16, 2026 - 62.16 KBWorks with firefox 109.0 and later, android 120.0 and later- Real timings in the diagnostics panel. Three costs, kept apart, with each
shown as a share of wall-clock time on the page:
over 47s on page:
scan 312ms 0.7% (204 scans, 18422 els)
observer 88ms 0.2% (1310 calls)
retry 4ms 0.0% (12 ticks)
They are separated because they fail for different reasons, and 1.1.35
proved a healthy scan figure says nothing about the other two: it reported
1.0ms across 274 scanswhile the page was unusable, because the cost was in
the retry loop, which never callsscanRoot. The observer had the same blind
spot until 1.1.42. Now all three are visible, and on a phone, which is where
none of them could be read before.
The percentage is the number worth reading. Milliseconds alone mean nothing
without knowing over how long they accumulated.
Scan timing was already collected in release builds —reportStatsreturns
early whenDEBUGis false, so nothing ever reset it — it simply had no way
to be seen. Observer timing is now collected unconditionally too: two
performance.now()calls per callback cost far less than what they measure,
and a figure that only exists in a build nobody installs is not a
measurement. Retry timing is new.
In aDEBUGbuild these are a rolling 2s window rather than cumulative,
becausereportStatsresets them. Worth remembering before comparing figures
between the two builds.Source code released under MIT License
- Real timings in the diagnostics panel. Three costs, kept apart, with each
Version 1.1.47
Released Aug 16, 2026 - 60.88 KBWorks with firefox 109.0 and later, android 120.0 and later- The mobile feed was slow to catch up, and it was the extension's fault.
Confirmed by control test: with the extension disabled the same feed loaded
quickly and normally.
A label whose post Facebook hasn't rendered gets deferred to the reveal
observer — but it was also being queued into the retry loop, which
re-examines every entry every 50ms for a full 8 seconds. Those retries can
never succeed: the candidate has no box and won't get one until Facebook
reveals it, at which point the IntersectionObserver handles it anyway. On a
virtualised feed most sponsored labels take that path, so the queue filled
with work that was guaranteed to be wasted.
This is the 1.1.35 regression's shape reached from a different direction — a
flooded retry queue burning the 50ms loop — and the same lesson applies:
queue only what can actually resolve later. Deferred labels are now left to
the reveal observer, which is already waiting for exactly the event that
would make them resolvable.unfollowedlabels were already excluded on the same reasoning. This extends
it to the case virtualisation creates.- The diagnostics panel reports
reveals— how many scans the reveal path has
triggered. It should track how far you have scrolled; climbing while the page
is still means the observer is firing when it shouldn't.
Source code released under MIT License
- The mobile feed was slow to catch up, and it was the extension's fault.
Version 1.1.46
Released Aug 16, 2026 - 60.05 KBWorks with firefox 109.0 and later, android 120.0 and later- The breakage warning fired on every mobile page load, blaming Facebook for
the extension working correctly. Observed on a real device running 1.1.45:
[fbsb] 20 labels matched, none could be anchored to a post ... If this is
a phone, the mobile class was probably renamed.
The mobile class had not been renamed — the warning says so itself two lines
earlier, reporting the gate asmobile. What actually happened is that most
of a virtualised feed is unrendered at any moment, those candidates have no
box, and since 1.1.45 they are deliberately deferred to the reveal observer.
The 1.1.42 check counted every deferral as a failure to anchor, so it
tripped its threshold on any mobile feed within a second of loading.
A miss on a post Facebook hasn't rendered is now counted separately and
excluded from the threshold. What remains is what the check was written for:
labels that should have anchored against something rendered, and didn't.
This mattered beyond the noise. The warning is notDEBUG-gated, so it
reached anyone with a console open, and it pointed confidently at the wrong
cause — the exact failure mode 1.1.42 was built to prevent.- The diagnostics panel reports
deferredalongside the other counts. A large
number there next to a healthyanchoredis the reveal mechanism working,
not failing.
Source code released under MIT License
- The breakage warning fired on every mobile page load, blaming Facebook for
Version 1.1.45
Released Aug 16, 2026 - 59 KBWorks with firefox 109.0 and later, android 120.0 and laterSource code released under MIT License
Version 1.1.44
Released Aug 15, 2026 - 57.28 KBWorks with firefox 109.0 and later, android 120.0 and later- Ads kept appearing on a phone while the badge said posts were hidden.
Both statements were true. Facebook virtualises the mobile feed: only a
window of posts is rendered, the rest sit atdisplay: nonewith afiller
element reserving their scroll height. Measured on a live phone feed, 42 of
64 feed children were hidden at once, behind a filler 13,226px tall.
A virtualised-out post reportsoffsetWidth0 — it has no box. The width
rule infindMobilePostContainerread that as "narrower than 60% of the
feed" and returnednull, so every off-screen ad was classified, rejected,
and forgotten. Facebook then revealed it on scroll, unfiltered. The posts
that were on screen resolved normally, which is why the badge kept
climbing while ads stayed visible.
The rule exists to reject nested carousel items — real boxes that happen to
be narrow. It can say nothing about an element with no box at all, so it is
now applied only to elements that have one. "Not currently rendered" and
"too narrow to be a post" are different claims, and only the second is
evidence against something being a post.
Desktop is unaffected: nothing there is virtualised this way, so every
candidate has a box and the rule applies exactly as before.
Long blank gaps between posts are a separate problem with the same root.
Facebook sizes that filler assuming the posts it virtualised still occupy
their heights; hiding one shrinks the content without shrinking the filler.
This release does not address that.
It is also not yet known whether a hide applied while a post is virtualised
out survives Facebook revealing it — if Facebook overwrites the inline style,
the ad returns, and the observer would not notice because it watcheschildListonly, not attributes. That is the next thing to measure, and the
reason this ships as one change rather than two.Source code released under MIT License
- Ads kept appearing on a phone while the badge said posts were hidden.