02 Feb 2026
Planet Mozilla
Firefox Nightly: Profile, search and wallpaper bug fixes plus more! – These Weeks in Firefox: Issue 195
Highlights
- If you were running into any problems last week opening links from other applications, specifically with Firefox being foregrounded but not opening the URL, this should now be fixed in Nightly and Beta (bug 2010535, fixed by Mossop). Please file a bug if you've updated your browser and the bug is still happening.
- Dão & Moritz continued their work on the new separate search bar, adding a 'x' to clear the input, respecting the browser.search.openintab preference, matching the search history to the legacy bar. This new version of the separate search bar is enabled by default in Nightly.

- Irene Ni enabled uploaded wallpapers to appear in container new tabs by propagating the wallpaper selection into container contexts.
- Mike Conley reduced CPU/battery drain from animated about:newtab backgrounds when the tab isn't visible by pausing animations when backgrounding the tab.
Friends of the Firefox team
Resolved bugs (excluding employees)
New contributors (🌟 = first patch)
- 🌟 Franccesco fixed a glitch in how the statistics panel displays in the DevTools Network Monitor
- Lorenz A helped get rid of an unused string property in the DevTools Style Editor code
- 🌟 Rocco Jiang helped make the border radius for some of our URL bar UI more consistent
Project Updates
Add-ons / Web Extensions
Addon Manager & about:addons
- Rob Wu investigated and fixed an issue that can prevent langpacks from being staged successfully as part of Firefox version updates (landed in 148, and will be included in a 147 dot release) - Bug 2006489
- Greg Stoll introduced a proper localized stings mapping table for Add-on Gated Site Permission, change needed as part of the work WebSerial DOM API - Bug 1826747
WebExtensions Framework
- Rachel Rusiecki contributed a nice cleanup of the WebExtensions internals by removing the extensions.manifestV3.enabled rollout pref - Bug 1804921
- Emilio investigated and fixed a drag and drop issue hit by WebExtensions action popup pages, regression introduced in Firefox 146 (by Bug 1933181 ) and fixed in Firefox 148 and 147 - Bug 2007274
WebExtension APIs
- Piro (TreeStyleTab extension developer) contributed a fix for browser.tabs.create unexpected rejection hit when openeredTabId is the tab id of a discarded tab - Bug 1762249
- Fix issue related to the extensions event page suspending while downloads.download API call is waiting for user input through the file chooser - Bug 2005953, Bug 2005963
- Fixed issue hit by the tabs API (and TreeStyleTab as a side effect of that) on builds were sync-about-blank is enabled (currently only Nightly builds) - Bug 2004525
- Fixed issue related to data set through browser.session.setTabValue not being preserved when the tab is moved between windows - Bug 2002643
- Fixed issue with declarativeNetRequest initialization at startup when one extension using declarativeNetRequest does not have any static DNR rules dataset declared in their manifest - Bug 2006233
- Arai contributed changes needed to allow declarativeNetRequest roles to apply successfully to cached web requests resources - Bug 1949623
AI Window
- assistant response markdown rendering with prosemirror 2001504
- initial dark theming for ai windows 2003058
- memory generation scheduling 2010143 (not yet generating with inference)
- various fixes to get tool calls and title generation working 2010640 2008658 2010905
- special token handling to hook up custom UIs 2008991
- adding ai window to new ai features about:preferences 2006250
DevTools
- Lorenz A removed unused code in the Style Editor (#2006115)
- Franccesco fixed the overflow of the Netmonitor so the whole Statistics panel is visible (#1984274)
- Bryan Thrall [:bthrall] added a way to re-parse scripts as module, which fixed adding breakpoint on GC-ed ES module files in the Debugger (#1605686)
- Nicolas Chevobbe [:nchevobbe] fixed an issue that would prevent adding breakpoint to very large file (for example some files in Slack) (#2010074)
- Reminder that there's a limit to the number of arguments to functions calls, which also applies to the spread operator
- /js/src/vm/ArgumentsObject.h#91-96
// Maximum supported value of arguments.length. This bounds the
// maximum number of arguments that can be supplied to a spread call
// or Function.prototype.apply. This value also bounds the number of
// elements parsed in an array initializer. NB: keep this in sync
// with the copy in builtin/SelfHostingDefines.h.
static const unsigned ARGS_LENGTH_MAX = 500 * 1000;
- Julian Descottes [:jdescottes] made the Responsive Design Mode UI to be destroyed when a tab is discarded (#2008708)
WebDriver
- Julian Descottes fixed a bug with the network.getData command which could throw when trying to get a chunked response body.
- Alexandra Borovova updated the reset behavior of the emulation.setGeolocationOverride and the emulation.setScreenOrientationOverride commands to align with the spec changes. With this update, when calling these commands to reset the override, e.g., a browsing context, only this override will be reset, and if there is an override set for a user context related to this browsing context, this override will be applied instead.
- Alexandra Borovova fixed user prompts open and close events to reference the correct context ID in case prompts are being opened from iframes on desktop and Android.
- Alexandra Borovova implemented support for automatic user prompt handling in WebDriver BiDi, which can be configured through capabilities with the session.new command.
- Henrik Skupin updated the WebDriver actions code to correctly calculate the target position for an asynchronous mouse click when a page is displayed with full zoom applied.
- Henrik Skupin improved several WebDriver classic commands to handle implicit and pageLoad timeouts in line with the script timeout, allowing null values to disable the timeouts.
- Henrik Skupin updated the internally vendored version of Puppeteer to v24.35.0.
Lint, Docs and Workflow
- ESLint and Prettier have been updated to the latest versions.
- This included a fix for eslint-plugin-jsdoc check-property-names rule which was raising some false-positives in firefox-main.
- eslint-env comments are being removed as ESLint v9 does not support them (use eslint-file-globals.config.mjs instead). ESLint v10 (currently in rc) will raise errors for them.
- More eslint-plugin-jsdoc rules have been enabled across the whole tree. These are the ones relating to valid-jsdoc. A few remain, but will need work by teams to fix the failures.
- There's now a linter to check for missing header guards in C++ code.
- The "Black" python formatter has now been replaced by "Ruff".
New Tab Page
- Linux users with new installs of Firefox were experiencing an issue where newtab was appearing blank (amongst other bugs). This appears to be related to content sandboxing and the XDG base directory support that was recently added for Linux builds. Emilio Cobos Álvarez is working on a fix in this bug.
- In the meantime, we've disabled all train-hop XPIs on Beta and Release for Linux builds. They will fallback to the built-in versions of New Tab instead.
- Morgan Reschenberg made the Weather opt-in callout reachable with macOS VoiceOver by correcting focus order and adding an accessible focus target.
- Scott Downe improved Top Sites relevance on newtab by deduping frecency-sorted items against older duplicates while preserving pins.
- Maxx Crawford added experiment logic to reorder the Weather opt-in button group to test clearer primary actions for enrolled users.
Profile Management
- Finn has been working through his onboarding bug list:
- bug 1947638, switching about:preferences to open the profile selector window in a dialog, not a subdialog
- bug 1950247, improve a11y by making headings on the edit profile page actually headings
- bug 2001276, excluding the ignoredSharedPrefs list when creating a new profile
- Mossop also continued making behavior more consistent across the toolkit profile service and the selectable profile service, fixed bug 2004345 - ensuring that, if a toolkit profile has a selectable profile group, we don't allow that toolkit profile to be deleted from about:profiles. Instead we warn the user.
Search and Navigation
- Address Bar
- Moritz fixed a multi-second jank when dragging large text over tabs or the address bar.
- Dale continued work on minor fixes to the new trust panel and enabled it for release in 149.
- Drew and Daisuke are starting the work on standardizing the UI for the various address bar result types (in-flight: 2010176, 2010177, 2010184) and their result menus (2010168, 2010171, 2010172).
- New Tab Search Bar
- Mike Conley changed this to be a self-contained re-usable component.
- Standard8 cleaned up the messages between parent and content processes after the search bar changes.
- Search Service
- Advance notice that this week we are planning on landing a change to the search service to change it from an XPCOM service to a Javascript singleton. This is part of work to remove the XPCOM interfaces as the service hasn't been accessed from C++ for a while.
- This will help reduce development overhead of needing to do full builds for interface changes.
- Advance notice that this week we are planning on landing a change to the search service to change it from an XPCOM service to a Javascript singleton. This is part of work to remove the XPCOM interfaces as the service hasn't been accessed from C++ for a while.
- Other interesting fixes
- The browser.urlbar.switchTabs.searchAllContainers preference has been removed.
- ESC key should now not save modified data in the Edit bookmark dialog when accessed from Star Icon.
UX Fundamentals
- We're disabling the felt-privacy error pages in Firefox 148 while we sort out a few small issues. Going to try to get this in for Firefox 149 with the new error page UI for all errors.
02 Feb 2026 6:04pm GMT
The Mozilla Blog: AI controls are coming to Firefox
AI is changing the web, and people want very different things from it. We've heard from many who want nothing to do with AI. We've also heard from others who want AI tools that are genuinely useful. Listening to our community, alongside our ongoing commitment to offer choice, led us to build AI controls.
Starting with Firefox 148, which rolls out on Feb. 24, you'll find a new AI controls section within the desktop browser settings. It provides a single place to block current and future generative AI features in Firefox. You can also review and manage individual AI features if you choose to use them. This lets you use Firefox without AI while we continue to build AI features for those who want them.
One place to manage your AI preferences
Firefox offers AI features to enhance everyday browsing. These features are optional, and they're easy to turn on or off.
At launch, AI controls let you manage these features individually:
- Translations, which help you browse the web in your preferred language.
- Alt text in PDFs, which add accessibility descriptions to images in PDF pages.
- AI-enhanced tab grouping, which suggests related tabs and group names.
- Link previews, which show key points before you open a link.
- AI chatbot in the sidebar, which lets you use your chosen chatbot as you browse, including options like Anthropic Claude, ChatGPT, Microsoft Copilot, Google Gemini and Le Chat Mistral.
You can choose to use some of these and not others. If you don't want to use AI features from Firefox at all, you can turn on the Block AI enhancements toggle. When it's toggled on, you won't see pop-ups or reminders to use existing or upcoming AI features.
Once you set your AI preferences in Firefox, they stay in place across updates. You can also change them whenever you want.
The browser that gives you a say
AI controls give you more say in how you move across the web.
We believe choice is more important than ever as AI becomes a part of people's browsing experiences. What matters to us is giving people control, no matter how they feel about AI.
If you'd like to try AI controls early, they'll be available first in Firefox Nightly. We'd love to hear what you think on Mozilla Connect.

Take control of your internet
Download FirefoxThe post AI controls are coming to Firefox appeared first on The Mozilla Blog.
02 Feb 2026 5:00pm GMT
31 Jan 2026
Planet Mozilla
Tarek Ziadé: Catching Code Complexity with a Local LLM
Performance issues in Python often don't look like bugs.
They don't crash, they don't fail tests, and they don't stand out in code review. They just quietly turn into cliffs when the input size grows.
This post is about one such performance fix in transformers, what it revealed, and a small experiment that came out of it: LoopSleuth, a local LLM-powered complexity scanner.
It Started With a Tokenizer Converter
While working on transformers, I fixed a performance issue in convert_slow_tokenizer.py that took a tokenizer conversion step from 4 minutes down to ~1 second when running on very large vocabularies (100k+ tokens).
The Test That Surfaced It
This started when CI flagged test_voxtral_tokenizer_converts_from_tekken as the slowest test in the suite.
The test loads mistralai/Voxtral-Mini-3B-2507 and forces the fallback path to TokenizersBackend.
That fallback triggers the slow→fast tokenizer conversion step - and that conversion was doing repeated .index() lookups inside a sort key, turning large vocabularies into a performance cliff.
The root cause was a classic scaling trap.
The Original Pattern
# BEFORE (simplified excerpt)
for rank, token in enumerate(bpe_ranks):
local = sorted(
local,
key=lambda x: (
bpe_ranks.index(x[0]),
bpe_ranks.index(x[1]),
),
)
(Simplified excerpt - the key issue is the repeated .index() inside the sort key.)
At first glance this looks harmless.
But list.index() is O(n).
And the real killer is that it happens inside a sorted() key function.
Sorting local means computing the key for every element, and each key performs two linear searches through bpe_ranks: sorted() calls the key function once per element (O(m)), and each key calls .index() twice (O(n)), so the total becomes O(m·n) - often a scaling trap when m and n are both large.
The Fix
# AFTER (reduces key computation from O(n) to O(1))
token_to_rank = {token: rank for rank, token in enumerate(bpe_ranks)}
for rank, token in enumerate(bpe_ranks):
local = sorted(
local,
key=lambda x: (
token_to_rank[x[0]],
token_to_rank[x[1]],
),
)
The optimization is simple:
- replace repeated linear searches with constant-time dictionary lookups
This doesn't eliminate all sorting work (the outer loop still sorts repeatedly), but it removes the quadratic lookup cost that was dominating runtime.
The takeaway wasn't just "use dicts" - it was that asymptotic traps often hide in perfectly valid Python idioms.
Could This Have Been Caught Automatically?
After landing that fix, I kept wondering:
How many other places in the codebase have the exact same pattern?
This wasn't a correctness issue:
- everything worked
- tests passed
- the slowdown only appeared at scale
And none of the linting tools I normally rely on flagged it.
Ruff's PERF rules catch obvious constructs like unnecessary list copies, but they don't reason about .index() inside a sort key.
In theory, a linter could detect patterns like:
- repeated
.index()inside loops .index()inside sort keys- nested iteration over growing structures
But most rule-based linters avoid making strong claims about asymptotic complexity.
That's a reasonable trade-off: linters are fast, deterministic, and low-noise - but they often miss scaling issues unless you add very specific custom rules.
This is where I started wondering whether an LLM could help fill the gap.
Scanning Transformers With Claude
As an experiment, I ran Claude Code over the repository with one question:
Find quadratic complexity patterns similar to the tokenizer converter bug.
The result was surprisingly useful.
It scanned ~3,000 Python functions across the codebase in a few minutes and flagged ~20 instances of the same anti-pattern:
.index()inside loops.index()inside sort keys- nested iteration patterns with superlinear blow-up at scale
About half were genuine hot-path candidates; others were technically quadratic but not performance-critical in practice.
For example:
ymls.sort(key=lambda x: results.index(x[:-4]))
Or:
aspect_ratios_ids[i, j] = supported_aspect_ratios.index(ratio)
All fixable with the same technique:
lookup = {val: idx for idx, val in enumerate(reference)}
This report was a great proof of concept.
But it relied on a large hosted model.
The Question Became: Can This Work Locally?
Instead of running a massive model in the cloud, I wanted to know:
- could a small local model catch these patterns?
- could we build something closer to a linter?
- could we automate complexity review?
That's how I ended up hacking together a small prototype I called LoopSleuth.
Why Rust + llama.cpp?
My first instinct was to build this as a Python script on top of transformers itself.
But I wanted this experiment to be:
- fast startup time
- easy CI binary distribution
- no Python runtime dependency
- easy to integrate into tooling
A single static binary makes it easy to drop into CI, like Ruff.
And honestly, I also wanted an excuse to explore the Rust ecosystem that powers tools like Ruff and Ty.
So LoopSleuth is written in Rust and uses:
rustpython-parserto extract functionsllama.cppbindings for local inference
In practice, a small model like Qwen2.5-Coder 3B (Q4) already gives surprisingly good results for this narrow task.
LoopSleuth: A Small Complexity Scanner
LoopSleuth is a CLI tool that:
- parses Python modules
- extracts functions (each function is analyzed in isolation: signature + body, without full module context)
- sends each function to a local LLM
- asks a focused question:
Does this contain patterns that may scale quadratically?
If the model answers "QUADRATIC", it also asks for an optimization suggestion.
This framing treats complexity as a heuristic warning (like a linter) rather than a mathematical proof.
How It Works
The prompt is deliberately simple and constrained:
Classify this function as OK or QUADRATIC.
Look for list.index(), nested loops, or linear operations inside loops.
Return only one word: OK or QUADRATIC.
This makes the model focus on structural patterns rather than trying to perform full dataflow analysis, and the constrained output format makes parsing reliable.
Example output:
⚠️ Functions with O(n²) complexity: 5
🔴 QUADRATIC COMPLEXITY DETECTED IN:
• bubble_sort
• find_duplicates
• remove_elements
• string_concatenation
• nested_comparison
Because it's a CLI, it can be used in a few practical ways:
- as a local complexity scanner during development
- as a lightweight pre-pass before calling a large cloud model (reducing token usage)
- as a GitHub Action on pull requests to catch patches that introduce quadratic behavior
Why Not Just Use Existing Linters?
Before building anything, I tried the usual suspects.
Tools like Ruff, Pylint, and performance-focused plugins can catch a lot:
- Pylint warns about string concatenation in loops (
consider-using-join) - Ruff has
PERFrules inspired by Perflint
But none of the linters I tried really caught the pattern that triggered this whole experiment:
- repeated
.index()lookups inside loops .index()inside sort key functions- nested iteration patterns that only become problematic at scale
These tools are excellent at enforcing specific rules, but they generally don't try to answer:
"Does this function scale quadratically with input size?"
That gap is what made the LLM approach interesting to explore.
A Quick Comparison
One thing I wanted to sanity-check early was whether existing linters would catch the same issues.
So I built a small test file with a handful of intentionally quadratic functions (nested loops, .remove() in loops, string concatenation, etc.) and ran:
- LoopSleuth
- Ruff (with
--select ALL) - Pylint
The results were pretty stark:
| Tool | Detects .index() in loop? |
Reports complexity? |
|---|---|---|
| Ruff | ❌ | ❌ |
| Pylint | ❌ | ❌ |
| LoopSleuth | ✅ | ✅ (heuristic) |
LoopSleuth flagged all 5 quadratic functions, while Ruff and Pylint flagged plenty of style and quality issues but neither directly reported algorithmic complexity problems.
This isn't really a criticism of those tools - they're simply not designed for that job.
I wrote up the full comparison here:
To be clear, there may be ways to approximate some of these checks with custom rules or plugins, and linters remain the first line of defense for code quality.
LoopSleuth is just exploring a different axis: scaling behavior.
Still an Experiment
LoopSleuth is not a replacement for linters.
It's a small experiment.
Traditional linters like Ruff or Pylint excel at catching specific code smells. But most scaling bugs don't come from a single construct. They come from composition:
- nested iteration
- repeated membership checks
- linear operations inside loops
Rule-based linters struggle to infer:
- "this
.index()is inside a hot path" - "this loop is over the same input size"
- "this becomes O(n²) at scale"
LLMs, even small ones, can often reason about these patterns more directly.
That said, LoopSleuth runs against isolated Python functions one by one, which means it doesn't yet understand:
- cross-function context
- runtime sizes
- whether a loop is actually hot in practice
Limitations
Like any heuristic tool, LoopSleuth has trade-offs:
False positives:
- small fixed-size loops that never scale
- code in non-hot paths
- patterns that look quadratic but have early exits
False negatives:
- complexity hidden across function calls
- indirect iteration patterns
- subtle algorithm choices
The accuracy depends heavily on prompt design and model choice.
Important: LoopSleuth is a screening tool, not a replacement for profiling or benchmarking. It flags patterns that may cause issues, but only real measurements can confirm actual performance problems.
More broadly, I'm interested in whether this approach can extend beyond complexity analysis to other classes of performance issues.
One direction would be to build a small library of prompts for:
- repeated tensor conversions
- hidden CPU/GPU sync points
- accidental re-tokenization
And in an ideal world, we could fine-tune a small model (like Qwen2.5-Coder 3B) to specialize on this kind of performance reasoning.
What's Next
If this experiment proves useful, here are some directions worth exploring:
- AST-based prefiltering to skip obviously safe functions
- Caching inference results to avoid re-analyzing unchanged code
- Training on real perf bugs from issue trackers and PRs
- GitHub Actions integration to catch regressions in CI
Right now LoopSleuth is a proof of concept, but these extensions could make it practical for real codebases.
Conclusion
LoopSleuth started as a simple question:
Could we catch quadratic complexity bugs automatically?
The answer is: not perfectly.
But even a small local model can spot surprising amounts of hidden O(n²) behavior.
And as codebases grow - especially ones like transformers - performance traps scale with them.
LoopSleuth is a small experiment toward making complexity visible earlier.
If you're interested, the project is here:
If you have examples of hidden scaling bugs or want to contribute detection patterns, I'd love to collect them as test cases. Feel free to try it locally or open an issue.
31 Jan 2026 12:00am GMT
