mozilla-firefox

Stars and mentions

Last 30 days, Oct 5 – Nov 3

GitHub stars0
mentions8
01234Oct 5Oct 19Nov 3
UTC days · repositories mozilla-firefox owns0 repositories

Package downloads

No package download history available yet.

Top repositories

mozilla-firefox’s repositories ranked by pull request authors, as on trending.

Loading

Contributes to

Repositories outside mozilla-firefox’s account where mozilla-firefox opened or reviewed pull requests in the past 365 days.

Who noticed mozilla-firefox

Everyone who starred or forked mozilla-firefox’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

8posts
Nov 40 posts
  1. One domain seller is 6% of the HSTS preload list --- title: "One domain seller is 6% of the HSTS preload list" published: true description: "5,691 entries on Chromium's HSTS preload list are one seller's domains for sale. Chromium cut them twice, they came back twice, and Firefox kept losing them." tags: "security, web, hsts, browsers" canonical_url: "blog.peng.ly" cover_image: "blog.peng.ly" ai_assisted: true --- The HSTS preload list is the list of sites your browser will only ever load over HTTPS, before it's even visited them once. It's built into Chrome and Edge, and Firefox makes its own copy from it. You get on by serving HTTPS on every subdomain, sending one header and filling in a form, and the idea is that it's for sites that care about HTTPS enough to promise it for good. I pulled Chromium's copy on 17 October and looked up all 95,147 entries, mostly out of nosiness about who's on it. The two addresses with the most names on them both belong to someone selling domains. !Who's on the HSTS preload list Who's on the HSTS preload list (the data) _Chromium's entries by the first IPv4 address they resolve to, 17 October 2026. Below the top two, every row is a host with lots of customers or one company's own sites. Cloudflare's behind far more than any of these, but spread over lots of addresses._ That's 5,691 entries between them, about one in every seventeen on the list. Pick one at random and you'll get a white page with the name in capitals, like "HAOMIN.COM MAY BE AVAILABLE!", a button to buy it on GoDaddy and "Proudly Powered by GKJ.com" at the bottom. GKJ's own site says "You came looking for a domain. We probably already own it", and puts the count at 5,000+. Who's registered them is redacted, so I'm going on their word that it's one seller. I fetched them slowly in random order until the server started telling me to go away. 498 of the 511 that answered were that sale page, 7 more said "UNDER CONSTRUCTION" in the same style, and all 511 sent max-age=63072000; includeSubDomains; preload. There's nothing against the rules in that. When I ran 20 of them through Chromium's own checker this afternoon, all 20 said "Satisfies requirements". Why a domain seller bothers, I don't know. The few pages the Wayback Machine kept from 2022 to 2024 come from the same kit, with the name in capitals over a slogan like "IN GOD WE TRUST", and the 2022 ones already sent the header. So my guess is it just comes with whatever makes the pages. It does matter to whoever buys one, though. They get a domain that browsers will only load over HTTPS, and every subdomain under it too, so nas.theirname.com with a self-signed certificate on the home network won't load, because HSTS takes away the button that lets you click through the warning. Getting off the list means asking for removal and then waiting, which hstspreload.org reckons "may take 6-12 weeks to reach most Chrome users, and may take longer for other browsers". You can check any name first with curl "https://hstspreload.org/api/v2/status?domain=haomin.com", which says "status": "preloaded" for this one. Chromium cut them twice !This seller's names on Chromium's list This seller's names on Chromium's list (the data) _How many of today's 5,691 names were on the list at the end of each month, January 2022 to October 2026._ Nine in ten of them went on in one bulk update in June 2022. In April 2023 Chromium pruned every domain that had "repeatedly failed to meet the continued requirements", which took out 41% of the list and 91% of this seller's names. They went back through the same form as everyone else, and most were back on within six months. Then a bulk update in August 2025 did it again, cutting the list from 171,166 entries to 95,329 and the seller from over 5,500 to 405. Of 40 other names it removed, every one still fails today, mostly because nothing answers, so it wasn't a bad clear-out. It just took these with it, and 4,659 of the names on the list today have now been cut twice. They were back within months again, and because that update cleared out so much else, this seller's now pretty much all of the list's growth. Since August 2025 the whole list has shrunk by 182 entries, while this one block grew by 5,286. Firefox kept losing them Firefox doesn't take Chromium's list as it is. A script visits every site on it about once a week and drops the ones that don't send the header. Visiting thousands of sites on one server all at once is exactly what this server doesn't like. When I fetched all 5,691 at 64 at a time, nearly every answer was a 429 Too Many Requests, and not one of those had the header. So in Firefox they came and went depending on how the week's run went: !This seller's names on Firefox's list This seller's names on Firefox's list (the data) _Each update to Firefox's preload list since June 2026. Mozilla's fix went in on 5 October, between the 2 and 6 October updates._ Ryan VanderMeulen at Mozilla spotted it last month. In bug 2075853 he wrote that Chromium's list "holds thousands of domains of one hosting operator in a contiguous block, all served from two addresses", and that checking them in list order meant "the operator answered with 429 for thousands of hosts that otherwise send a valid header, which kept them off the preload list". The fix landed on 5 October. The script now shuffles the list, tries 429s again at the end, and an error page without the header doesn't knock a site off anymore. Before that, a run where more of them got through, like 26 September's, was gone again the week after. Since then every update has kept what it had and added more, up to 2,275 on 16 October. That's still well short of 5,691, and the bug says why: "the operator still limits the rate over the run as a whole". Chromium's checker gets waved through I'd assumed Chromium's culls tripped over the same 429s. Its removal job checks every bulk entry weekly, only looks at the header and not the status code, and queues a domain for removal once it's failed for a month. But Chromium's checker calls itself hstspreload-bot, so this evening I got the server annoyed with 300 quick requests, then asked ten more names for their page under four different User-Agents: | User-Agent | What came back | HSTS header | | --- | --- | --- | | Chrome | 429 | no | | Firefox | 429 | no | | Googlebot | 429 | no | | hstspreload-bot | 200 | yes | Same for all ten names. So the rate limit lets Chromium's checker straight through, and it's everyone else who gets the 429, Firefox included. What it did in 2023 and 2025 I can't tell, and the Wayback Machine won't show me enough from just before either cull to find out. They pass Chromium's checks now, and they were cut anyway.

  2. Most big Firefox add-ons still don't say what they collect --- title: "Most big Firefox add-ons still don't say what they collect" published: true description: "Mozilla said every Firefox add-on would declare its data collection by mid-2026. Most popular ones still don't, and one that did got buried in one-star reviews." tags: "privacy, firefox, browserextensions, mozilla" canonical_url: "blog.peng.ly" cover_image: "blog.peng.ly" ai_assisted: true --- Here's what Firefox showed me this morning when I went to install two password managers: !Firefox's prompt to add Bitwarden Password Manager. It lists six required permissions, like accessing your data for all websites and the clipboard, then an optional setting for private windows. There's no data collection section. _Bitwarden 2026.10.0, out on 15 October_ !Firefox's prompt to add LastPass. It lists five required permissions, then a Required data collection section saying the developer says it collects browsing activity, website activity, authentication information and location, then a pre-ticked box to share technical and interaction data. _LastPass 4.154.2_ Which one's telling you more? LastPass, obviously. It's got a whole "Required data collection" section, and a pre-ticked box for sending the developer technical data. Bitwarden's just stops after the permissions, and it'd be easy to read that as "collects nothing". But, that's not what this actually means.. An add-on that really collects nothing will say so in the prompt - Proton Pass's says "The developer says this extension doesn't require data collection." Bitwarden's has no data section at all, since nobody's made Bitwarden fill it in. Since 3 November 2025, every new add-on on addons.mozilla.org (AMO) has had to put a data_collection_permissions key in its manifest, even if it's only to say "none", and Firefox 140+ shows it in that prompt. Add-ons that already existed were let off, but Mozilla said that wouldn't last: > In the first half of 2026, Mozilla will require all extensions to adopt this framework. But don't worry, we'll give plenty of notice via the add-ons blog. The first half of 2026 ended in June. The add-ons blog hasn't mentioned it since, and nothing's changed. AMO's API reports the key for every add-on, so I went through the top 1,000 by users to see how far the labels have actually got. Updated since July, and still nothing Of the top 1,000, 776 show nothing. Most of those haven't shipped a new version since November, so they never had a reason to add the key. The 21 made since November are fine, 20 of them declare something, which is what happens when it's actually enforced. So I split the other 979 by when their current version was uploaded: | Current version uploaded | Says something | | :------------------------ | ---------------: | | Before 3 Nov 2025 | 2 of 475 (0.4%) | | 3 Nov 2025 to 30 Jun 2026 | 57 of 161 (35%) | | Since 1 Jul 2026 | 145 of 343 (42%) | Adblock Plus, AdGuard, Dark Reader, DuckDuckGo, Ghostery, NoScript, 1Password, MetaMask and Bitwarden have all shipped since July, and none of them say anything. I downloaded each one to check the API wasn't lying, and the key just isn't in their manifests. They're 9 of the 198 in that last row that left it out, and those 198 have about a third of the top 1,000's daily users between them. That's Mozilla's deadline, not theirs. Nobody told them to add it (more on that next), and silent doesn't mean they're collecting anything. uBlock Origin and Privacy Badger both bothered to say "none". It just means the prompt can't tell you either way. Nothing makes them Why haven't they? Mostly beacuse they don't have to. AMO's upload check upload check only turns a missing key into an error when it's a brand new add-on uploaded through the developer hub. An update gets a warning, and that warning still says the key "will be required for new versions of existing extensions in the future". Mozilla's docs say older add-ons "will have to at a later date", with no date. The nearest thing to news is a reply from Mozilla's add-ons team on thier forum, on 10 September: > Even though we haven't retroactively asked all the published Add-ons to comply with this policy, we do it every time they submit a new version/update. From the code, I think "asking" there is that warning. The same reply says "I can't promise anything specific right now". Being honest costs you When an older add-on adds the key for the first time and says it collects something, every existing user gets asked to agree, and if they say no the update doesn't install. Saying "none" doesn't do that. Firefox has a way round it: a has_previous_consent flag that says users already agreed to whatever the add-on did before, so the update goes through without a prompt. But AMO's linter has rejected that flag since October 2025, in a change titled "Prevent the use of has_previous_consent for now". Return YouTube Dislike found out what that means. It's got about 900,000 daily users, and on 7 September it shipped its first update in nearly two years, declaring personally identifying information, browsing activity, website content and website activity. Its developer says all it gets is a random user ID and the IDs of the videos, which is how it counts dislikes in the first place. That sounds like a lot for a dislike button, but Mozilla's examples for personally identifying information include "ID numbers", and browsing activity covers even "domains", so I can see how it got there. Here's what happened on its reviews page: !One-star reviews of Return YouTube Dislike on AMO One-star reviews of Return YouTube Dislike on AMO (the data) _A week at a time, from Mondays, up to 11 October. Version 4.0.6 came out on 7 September_ It went from about one a week to 101 in a week. "Spyware", "data mining scam", "At what point does a youtube dislike add-on need my personal data?". One reviewer was sure the developer's account had been hacked. My favourite was this one, which sort of gets it and still gives one star: > The fact that the data processing method hasn't changed means it was being collected all along. To be fair to the reviewers, the same update added premium statistics (likes over time, maps by country), and some of the reviews are about that. But about half of the 224 one-star reviews since 7 September mention the data or the permissions, some quoting Firefox's prompt word for word. The developer put it on Mozilla's forum as "calling my reviews section a shitstorm is an understatement". The first post in that same thread is from SponsorBlock's developer. It's a similar sort of add-on, talking to its own server about YouTube videos, and they were working out which boxes they'd have to tick. Probably browsing activity, they thought, maybe website content. SponsorBlock's current version came out in July and doesn't declare anything, so its 820,000 daily users get a nice clean prompt. So nothing makes the quiet add-ons say anything, and the flag that would let the honest ones say it without a fight has been blocked by AMO "for now" for a year. Untill one of those changes, the add-ons that tell you the most look the worst, and the ones that tell you nothing look fine.

  3. 301 redirect change doesn't take effect for some users On the documentation site, /docs redirects to the pages for the latest version. Links on other sites, such as the product site, point to /docs. For the v4 release, the nginx config was edited to change the target from /docs/v3/ to /docs/v4/. location = /docs { return 301 /docs/v4/; } 301 is the status code meaning the requested URL has moved permanently to another URL. The /docs URL itself will not change, so it was set up as a 301. After the change, reports keep coming in: "When I open it from the link on the product site, I get v3." Running curl against /docs locally returns a 301 to v4. $ curl -sI https://<host>/docs HTTP/1.1 301 Moved Permanently Location: https://<host>/docs/v4/ I reproduced this with nginx and browsers and measured why some users land on v3. Impact Some of the users who opened /docs from the product site link while it pointed to v3 keep reading the v3 pages after the v4 release. No error appears, so only the users who notice send a report. The ones who don't notice go on configuring and working by the v3 instructions. This does not go away, however many days pass. It happens again every time a release changes the target. Reproduction I reproduced the scenario above with nginx 1.31.6 and three browsers driven by Playwright 1.63.0: Chromium, WebKit (the engine behind Safari), and Firefox.[^env] [^env]: Run on GitHub Actions (ubuntu-24.04). Browser versions: Chromium 153.0.8010.12, WebKit 26.6, Firefox 155.0. The logs and curl output in the text have the per-condition paths and host names rewritten to /docs and <host>. The config, scripts, and raw logs are in show-your-work. A page containing only a link to /docs is served on a host name separate from the documentation site, and /docs is opened from that page. /docs responds with the same return 301 as above. While the target is v3, open /docs from the link page. Then change the nginx config to v4 and reload. After that, open /docs from the link page three times: right after the switch, 70 seconds later, and after restarting the browser. Each time, record which version's page the browser landed on. Chromium and WebKit landed on v3 all three times. Firefox landed on v4 from right after the switch.[^run] [^run]: Verification results: CI job When /docs was opened while the target was v3, all three browsers left a /docs line in the nginx access log. <client IP> - - [15/Oct/2026:23:13:51 +0000] "GET /docs HTTP/1.1" 301 169 ... After the switch, there was no /docs line for any of the three times Chromium and WebKit landed on v3. Firefox, which landed on v4, left a /docs line all three times. Chromium and WebKit went to v3 without asking the server. Why v3 keeps showing The /docs 301 does not say how long it may be stored and reused. How to treat a 301 without that lifetime is up to the browser. Chromium treats it as never expiring and WebKit as valid for one year, and both reuse the stored 301 without asking the server.[^chromium][^webkit] So users who stored the 301 to v3 still land on v3 after the switch. Firefox does not reuse the stored 301 as is. It asks the server every time the page is opened. These are the headers of the /docs 301: $ curl -sI https://<host>/docs HTTP/1.1 301 Moved Permanently Server: nginx/1.31.6 Date: Thu, 15 Oct 2026 23:14:03 GMT Content-Type: text/html Content-Length: 169 Location: https://<host>/docs/v4/ Connection: keep-alive Cache-Control: max-age=<seconds> is how a response tells the browser it may store the response and use it without asking for that many seconds. This response has no such header. Nor does it have Last-Modified, the header that gives the time the file was last modified. Firefox computes the lifetime of a 301 without a stated lifetime from Last-Modified.[^firefox] This response has no Last-Modified, so the lifetime is 0, which means the response is already expired when it is stored. That is why Firefox asks the server every time. [^chromium]: Chromium's http_response_headers.cc (GetFreshnessLifetimes). A 301 without a stated lifetime never expires. A 302 gets 0. [^webkit]: WebKit's CacheValidation.cpp (computeFreshnessLifetimeForHTTPFamily). A 301 without a stated lifetime gets one year. Anything else gets a lifetime computed from Last-Modified, or 0 if there is none. [^firefox]: Firefox's nsHttpResponseHead.cpp (ComputeFreshnessLifetime). A 301 without a stated lifetime gets a lifetime computed from Last-Modified, or 0 if there is none. A 302 without a stated lifetime gets 0. Fix Switch to a 302, or give the 301 a lifetime with max-age. Either way, users who have already stored the 301 to v3 stay on v3. Those users are covered in the notes at the end. Use a 302 302 is the status code meaning the requested URL has moved temporarily to another URL. In nginx: location = /docs { return 302 /docs/v4/; } For a 302 with neither a stated lifetime nor Last-Modified, Chromium, WebKit, and Firefox all set the lifetime to 0 and ask the server for /docs every time it is opened.[^chromium][^webkit][^firefox] Add max-age to the 301 You can also keep the 301 and state a lifetime with max-age in Cache-Control. The verification used 60 seconds. location = /docs { add_header Cache-Control "max-age=60"; return 301 /docs/v4/; } Headers added with add_header are also attached to the 301 sent by return. The tradeoff is that users who opened /docs just before a release land on the previous version for up to max-age seconds after the release. Which to choose I choose the 302. At the start, /docs got a 301 because its URL does not change. A 301, however, says that the move from /docs to its target is permanent. The target changes with every release, so the move is temporary. I don't choose max-age. It saves one round trip to /docs during the lifetime, but in exchange, users who opened /docs just before a release land on the previous version. Effect of the fixes With a 302 With the config already using return 302 while the target was v3, I opened /docs with the same steps as the reproduction.[^run] | Time | Chromium | WebKit | Firefox | |---|---|---|---| | Right after the switch | v4 | v4 | v4 | | 70 seconds later | v4 | v4 | v4 | All three browsers landed on v4 from right after the switch. With max-age on the 301 With the config already returning the 301 with max-age=60 while the target was v3, I opened /docs with the same steps as the reproduction.[^run] | Time | Chromium | WebKit | Firefox | |---|---|---|---| | Right after the switch | v3 | v3 | v3 | | 70 seconds later | v4 | v4 | v4 | Right after the switch, Firefox also used the stored 301 and landed on v3. At 70 seconds, after the 60-second lifetime had passed, all three landed on v4. Notes Affected browsers Chromium-based browsers and Safari keep landing on v3. Firefox 139 and later are not affected. Firefox 138 and earlier, and the ESR releases based on them, are. Firefox 138 and earlier treated a 301 without a stated lifetime as never expiring.[^ff-bug] [^ff-bug]: Mozilla Bug 1957619. Chromium stores the 301 separately depending on whether the page was opened from a link on another site or typed into the address bar.[^chromium-key] In the verification, after the 301 to v3 was stored by opening /docs from the link page and the target was switched to v4, opening /docs directly, without going through a link on another site, took Chromium to v4 and WebKit to v3.[^run] [^chromium-key]: Chromium's http_cache.cc. Top-level navigations coming from another site are stored under a separate cache key. To check this in your own Chrome, open /docs from a link on another site, the way users do. Users who still land on v3 after changing to 302 I also ran the case where the 301 to v3 is stored first and the config is changed to return 302 at the switch, opening /docs with the same steps as the reproduction. Chromium and WebKit landed on v3 all three times: right after the switch, 70 seconds later, and after restarting the browser.[^run] They keep using the stored 301 to v3, so the change to 302 never reaches these users. Clear-Site-Data is a header that tells the browser receiving it to delete that host's cache and other stored data. In the verification, the v4 page, on the same origin as /docs, returned this header. location /docs/v4/ { add_header Clear-Site-Data '"cache"'; } For users who opened this page, the stored 301 was deleted, and Chromium and WebKit also landed on v4 from /docs.[^run] The header only reaches users who open a v4 page. Users being sent to v3 never get to a v4 page from /docs. Note that the header tells the browser to delete the entire cache for that host, not just the v4 page. The header also works only over HTTPS (or on localhost).[^csd] [^csd]: W3C's Clear Site Data. "cache" deletes the cache for the same host as the response URL (4.2.3). If the response URL is not treated as secure (HTTPS and localhost URLs are), nothing is deleted (4.2). Setups that give the redirect a lifetime If expires is set at the server level, the 301 sent by return in a location also gets a Cache-Control header.[^nginx-expires] In the verification, with expires 30d at the server level and only return 301 in the /docs location, the 301 for /docs got Cache-Control: max-age=2592000 (30 days).[^run] Users land on the previous version for up to 30 days after the release. [^nginx-expires]: nginx's ngx_http_headers_module. expires adds Expires and Cache-Control to responses with status code 301 as well. Setups not tested - Sites with a registered Service Worker that answers requests to /docs with a locally held response - Setups with a CDN in front of nginx that stores the /docs 301 and serves it In both, the request does not reach nginx, so the /docs lines in the access log cannot tell you whether the browser used a stored 301.

  4. 301 リダイレクトの変更が一部の利用者に反映されない ドキュメントサイトの /docs は最新版のページへのリダイレクトにしている。製品サイトなど、ほかのサイトに貼るリンクは /docs に向けている。v4 のリリースで、nginx の設定を書き換え、行き先を /docs/v3/ から /docs/v4/ に変えた。 location = /docs { return 301 /docs/v4/; } 301 は、リクエストされた URL が恒久的に別の URL に移ったことを表すステータスコードだ。/docs という URL はこの先も変えないので、301 にした。 変えたあとも、「製品サイトのリンクから開くと v3 が出る」という問い合わせが来る。手元で /docs を curl で叩くと、v4 への 301 が返ってくる。 $ curl -sI https://<host>/docs HTTP/1.1 301 Moved Permanently Location: https://<host>/docs/v4/ この状態を nginx とブラウザで再現し、v3 に着く理由を実測で確かめた。 影響 v3 の時期に製品サイトのリンクから /docs を開いた利用者の一部は、v4 のリリース後も v3 のページを読み続ける。エラーは出ないので、問い合わせてくるのは気づいた利用者だけだ。気づかない利用者は v3 の手順で設定や作業を進める。 この状態は何日経っても直らない。リリースで行き先を変えるたびに、同じことが起きる。 再現 冒頭の状態を、nginx 1.31.6 と、Playwright 1.63.0 で動かす Chromium、WebKit(Safari と同じエンジン)、Firefox で再現した1。 ドキュメントサイトとは別のホスト名で、/docs へのリンクだけを置いたページを配信し、そこから /docs を開く。/docs は冒頭と同じ return 301 で返す。 行き先が v3 のあいだに、リンクのページから /docs を開く。nginx の設定を v4 に書き換えてリロードする。そのあと、向け直した直後、70 秒後、ブラウザを再起動したあとの 3 回、リンクのページから /docs を開き、どのバージョンのページに着いたかを記録した。 Chromium と WebKit は 3 回とも v3 に着いた。Firefox は向け直した直後から v4 に着いた2。 v3 の時期に開いたときは、3 つのブラウザとも nginx のアクセスログに /docs の行を残している。 <利用者の IP> - - [15/Oct/2026:23:13:51 +0000] "GET /docs HTTP/1.1" 301 169 ... 向け直したあと、Chromium と WebKit が v3 に着いた 3 回とも、/docs の行はなかった。v4 に着いた Firefox は 3 回とも /docs の行を残している。Chromium と WebKit は、サーバーに問い合わせずに v3 に飛んでいる。 v3 を表示し続ける原因 /docs の 301 には、保存して使ってよい期限が書かれていない。期限のない 301 をどう扱うかはブラウザが決める。Chromium は期限なし、WebKit は 1 年として、保存した 301 をサーバーに問い合わせずに使う34。そのため、v3 行きの 301 を保存した利用者は、向け直したあとも v3 に着く。Firefox は保存した 301 をそのまま使わず、開くたびにサーバーに問い合わせる。 /docs の 301 のヘッダは次のとおりだ。 $ curl -sI https://<host>/docs HTTP/1.1 301 Moved Permanently Server: nginx/1.31.6 Date: Thu, 15 Oct 2026 23:14:03 GMT Content-Type: text/html Content-Length: 169 Location: https://<host>/docs/v4/ Connection: keep-alive Cache-Control: max-age=<秒数> は、ブラウザが応答を保存し、その秒数のあいだ問い合わせずに使ってよいという期限の書き方で、この応答にはない。ファイルの更新時刻を表す Last-Modified もない。 Firefox は、期限の書かれていない 301 の期限を Last-Modified から計算する5。この応答には Last-Modified がないので期限は 0、つまり保存した時点で期限切れになる。そのため Firefox は開くたびにサーバーに問い合わせる。 対策 302 にするか、301 に max-age で期限を書く。すでに v3 行きの 301 を保存した利用者は、どちらにしても v3 に着いたままになる。この利用者は補足で扱う。 302 にする 302 は、リクエストされた URL が一時的に別の URL に移ったことを表すステータスコードだ。nginx では次のように書く。 location = /docs { return 302 /docs/v4/; } 期限も Last-Modified も書かれていない 302 は、Chromium、WebKit、Firefox とも期限を 0 にし、開くたびに /docs をサーバーに問い合わせる3:14:15:1。 301 に max-age を付ける 301 のまま、Cache-Control の max-age で期限を書くこともできる。検証では 60 秒にした。 location = /docs { add_header Cache-Control "max-age=60"; return 301 /docs/v4/; } add_header で足したヘッダは、return で返す 301 の応答にも付く。 代償として、リリースの直前に /docs を開いた利用者は、リリースから最長で max-age の秒数のあいだ前のバージョンに着く。 どちらを選ぶか 私は 302 を選ぶ。冒頭では /docs の URL を変えないので 301 にした。301 は、/docs から行き先への移動が恒久的であることを表す。リリースのたびに行き先が変わるので、この移動は一時的だ。 max-age は選ばない。期限のあいだ /docs への 1 往復は省けるが、その代わりに、リリースの直前に開いた利用者が前のバージョンに着く。 対策の効果 302 にした場合 v3 の時期から return 302 で返す設定にして、再現と同じ手順で /docs を開いた2:1。 時点 Chromium WebKit Firefox 向け直した直後 v4 v4 v4 70 秒後 v4 v4 v4 3 つのブラウザとも向け直した直後から v4 に着いた。 301 に max-age を付けた場合 v3 の時期から 301 に max-age=60 を付けて返す設定にして、再現と同じ手順で /docs を開いた2:2。 時点 Chromium WebKit Firefox 向け直した直後 v3 v3 v3 70 秒後 v4 v4 v4 向け直した直後は、Firefox も保存した 301 を使い、v3 に着いた。期限の 60 秒が過ぎた 70 秒後には、3 つとも v4 に着いた。 補足 起きるブラウザ v3 に着き続けるのは、Chromium 系のブラウザと Safari だ。Firefox は 139 以降なら起きない。138 以前と、それを元にした ESR では起きる。138 以前の Firefox は、期限の書かれていない 301 を期限なしとして扱っていた6。 Chromium は別サイトのリンクから開いたときと、アドレスバーに打ったときとで、301 を別々に保存する7。検証で、リンクのページから開いて v3 行きの 301 を保存し、v4 に向け直したあと別サイトのリンクを経ずに /docs を直接開くと、Chromium は v4 に、WebKit は v3 に着いた2:3。 手元の Chrome で確かめるときは、利用者と同じく、別サイトのリンクから開く。 302 に変えても v3 に着く利用者 301 で v3 行きを保存させたあと、向け直すときに return 302 に変えた設定でも、再現と同じ手順で /docs を開いた。Chromium と WebKit は、直後、70 秒後、再起動後の 3 回とも v3 に着いた2:4。保存した v3 行きの 301 を使い続けるので、302 に変えたことはこの利用者に伝わらない。 Clear-Site-Data は、応答を受け取ったブラウザに、そのホストのキャッシュなどを消させるヘッダだ。検証では、/docs と同じオリジンの v4 のページから、このヘッダを返した。 location /docs/v4/ { add_header Clear-Site-Data '"cache"'; } このページを開いた利用者は、保存した 301 が消え、Chromium と WebKit でも /docs から v4 に着いた2:5。ヘッダが届くのは、v4 のページを開いた利用者だけだ。v3 に飛ばされている利用者は、/docs からは v4 のページに着かない。 ただし、このヘッダが消すように指示するのは、v4 のページだけでなく、そのホストのキャッシュ全体だ。また、このヘッダは HTTPS(または localhost)でしか効かない8。 リダイレクトに期限が付く構成 server の階層で expires を設定していると、location の return で返す 301 にも Cache-Control が付く9。検証で server の階層に expires 30d を書くと、location に return 301 だけを書いた /docs の 301 に、Cache-Control: max-age=2592000(30 日)が付いた2:6。利用者はリリースから最長 30 日、前のバージョンに着く。 確かめていない構成 Service Worker を登録していて、Service Worker が /docs へのリクエストに手元の応答を返すサイト nginx の前に CDN を置き、CDN が /docs の 301 を溜めて返す構成 どちらも、リクエストが nginx まで届かないので、ブラウザが保存した 301 を使ったかを、アクセスログの /docs の行では見分けられない。 脚注 GitHub Actions(ubuntu-24.04)で動かした。ブラウザのバージョンは Chromium 153.0.8010.12、WebKit 26.6、Firefox 155.0。本文のログと curl の出力は、条件ごとのパスとホスト名を /docs と <host> に書き換えている。設定・スクリプト・生ログは show-your-work にある。 ↩︎ 検証結果:CI のジョブ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ Chromium の http_response_headers.cc(GetFreshnessLifetimes)。期限の指定がない 301 は期限なし、302 は 0 にする。 ↩︎ ↩︎ WebKit の CacheValidation.cpp(computeFreshnessLifetimeForHTTPFamily)。期限の指定がない 301 は 1 年、それ以外は Last-Modified から期限を計算し、なければ 0 にする。 ↩︎ ↩︎ Firefox の nsHttpResponseHead.cpp(ComputeFreshnessLifetime)。期限の指定がない 301 は、Last-Modified から期限を計算し、なければ 0 にする。期限の指定がない 302 は 0 にする。 ↩︎ ↩︎ Mozilla の Bug 1957619。 ↩︎ Chromium の http_cache.cc。別サイトから来たトップレベルの遷移を、別のキャッシュキーで保存する。 ↩︎ W3C の Clear Site Data。"cache" は応答の URL と同じホストのキャッシュを消す(4.2.3)。応答の URL が HTTPS や localhost のような安全とみなす URL でなければ、何も消さない(4.2)。 ↩︎ nginx の ngx_http_headers_module。expires は、ステータスコードが 301 の応答にも Expires と Cache-Control を足す。 ↩︎

    1 like
  5. these binds occur in code right here and are completely hardcoded. luckily, it's javascript, which is very malleable at runtime. press `Ctrl+Shift+J` to open the browser console, and then the following snippet removes the behaviour: (await import("chrome […]

  6. Be that as it may, they can still roll out the passkey attestation requirement gradually, -- "flip the switch" just a few thousand users at a time -- so that at no one point in time is there a large blip in users. Truth be told, tech platforms don't even really need to measure what portion of users' devices support remote attestation, since the overwhelming majority of devices will support it. They just need to know that users are passkey-capable in the first place; once enough users have passkeys in any form, they can start requiring remote attestation. As for the browser permission prompt: Firefox simply warns you that the site is "requesting extended information about your security key [sic] which may affect your privacy." [1] It does not make it at all clear to the user that what's really happening is that the website is gating acces behind a specific approved software stack. If I use a non-widely-used OS or device, what website is going to approve its attestation credentials? [1] github.com/mozilla-firefox/firefox/blob/8e9ac3e12…

  7. Buttons don't work for some users after a deploy Once a browser has fetched an index.html served without Cache-Control (the header that says how long a response may be cached), it keeps using its own copy without going back to the server. That period is 10% of the time since index.html was last modified. If you deploy during that period, reopening the site still runs the previous version's page. The files that page loads later are gone from the server, so when a user uses a feature the page hasn't loaded yet, the load fails and clicking the button does nothing. An error tracker (Sentry or similar) shows this line: TypeError: Failed to fetch dynamically imported module: <chunk URL> It means a JS file the page tried to load at runtime with import() could not be fetched. A chunk is a separate file the build splits some of the page's features into. It is loaded for the first time when the user clicks a button or similar. The error hits only users who use a feature the previous version hadn't loaded yet. Everyone else keeps running the previous version with no error. On a site that deploys once a week, the previous version keeps running in users' browsers for up to about 17 hours after the deploy. Browsers that open the site for the first time after the deploy, or that reload, run the new version, so checking internally may not reproduce it. Users running the previous version don't get a fix even after you deploy it. I reproduced this with a Vite app served by nginx with its default config, and measured how long the previous version runs and what each fix does. What is happening For responses without Cache-Control, the HTTP caching spec[^heuristic] lets a cache guess how long it may use its copy without going back to the server, and gives 10% of the time since Last-Modified (when the file was last modified) as a typical value. Chromium uses exactly 10% of the difference between the time the response was sent (Date) and Last-Modified.[^chromium] Below, the difference between Date and Last-Modified at fetch time is called the time since modification. [^heuristic]: RFC 9111, section 4.2.2. [^chromium]: The line lifetimes.freshness = (date_value - last_modified_value.value()) / 10; in Chromium 153.0.8010.12's GetFreshnessLifetimes. Files with a hash (a string the build derives from a file's contents and puts in its name) get a new name whenever their contents change. Which version's files get loaded is decided only by the file names written in index.html, whose own name never changes. Without Cache-Control, the browser decides when to fetch that index.html again. When build artifacts are baked into an image, Last-Modified is the build time. On a site that deploys once a week, a browser that opened the site just before the next deploy uses its own copy of index.html, without going to the server, for about 17 hours, 10% of 7 days. 1 2 3 4 5 | | | | | server serves [=====old=====][============new=============] browser runs [===========old===========][=new] <- 7 days -><------- 16.8 hours ------> The top row is the version the server serves. The bottom row is the version that runs when the browser that opened the site at 2 reopens it at each point in time. 1 is when the previous version was built (Last-Modified), 2 is when the user opened the site and fetched index.html (Date), 3 is the deploy, 4 is when the user reopened the site, and 5 is when 10% of the span from 1 to 2 has passed since 2. The span from 1 to 2 is compressed in the figure. If a deploy falls between 2 and 5, reopening the site still runs the previous version. After 5, index.html is fetched again and the new version runs. Verification I served a Vite app that loads a chunk when a button is clicked, using nginx with its default config. I opened the previous version, closed the browser, deployed by replacing the container, then relaunched the browser and opened the site again.[^env] | Time since modification | How long the previous version ran | |:---|:---| | 10 min | About 1 min | | 100 min | About 10 min | | 1000 min | About 100 min | | 6 days | About 14 h | Each was about 10% of the time since modification. Reopening the site in a new tab with the browser kept open gave the same result.[^keepopen] In browsers that hadn't clicked the button on the first open, clicking it while the previous version was running produced the error. In browsers that had clicked it, no error appeared. Reloading while the previous version was running fetched index.html again and switched to the new version. [^keepopen]: Compared at a time since modification of 100 minutes, side by side with relaunching the browser, within the same deploy. [^env]: The browser was Chromium headless shell 153.0.8010.12 (the build without a window) driven by Playwright 1.63.0, with nginx 1.30.5 and Vite 8.3.1. The time since modification was produced by shifting the modification time of the served files into the past when starting the container (nginx returns a file's modification time as Last-Modified). The time until reopening was produced with an Age header that the previous version's server added to index.html. The browser treats the response as already that many seconds old when it receives it (RFC 9111, section 4.2.3). At a time since modification of 10 minutes, the results matched actually waiting. Each condition ran 4 times in CI, for 12 trials per data point. The reproduction setup and scripts are in show-your-work. Checking your own environment The response headers on index.html tell you whether browsers keep using their own copy of it. The access log tells you whether any users ran the previous version and hit the error. Response headers Check the response headers on index.html with this command. Look at the same headers whether you serve from S3, a CDN, or a hosting service, or render the HTML on the server per request. curl -sI https://app.example.com/ | grep -iE '^(cache-control|expires|last-modified):' If the response has Last-Modified but neither Cache-Control nor Expires (the header that sets an expiry time), the previous version keeps running after a deploy for 10% of the time since modification. Access log From a browser that ran the previous version and hit the error, the new version's server receives only the following line and returns 404 (the status meaning the file doesn't exist). It is a request for the previous version's chunk, and the same browser sends no request for index.html. "GET /assets/feature-DHyZGV0s.js HTTP/1.1" 404 153 "http://127.0.0.1:18000/assets/index-DB4b__oJ.js" From a browser that had clicked the button under the previous version, not a single line arrives. Fixes There are three fixes: add no-cache to index.html, add max-age to it, or reload when loading fails. Add no-cache to index.html With Cache-Control: no-cache on index.html, the browser asks the server whether index.html has changed every time it opens the page.[^no-cache] If it hasn't, the server returns 304 (the status meaning not modified) and sends no body. [^no-cache]: RFC 9111, section 5.2.2.4. In nginx, add this: location = /index.html { root /usr/share/nginx/html; add_header Cache-Control "no-cache"; } The new version runs every time the site is reopened, and the window where the previous version runs disappears.[^compare] [^compare]: Using the same setup as with no fix, I opened an index.html with a time since modification of 100 minutes and compared different times until reopening. Each condition ran 4 times in CI, for 12 trials per data point. The cost is one extra round trip to the server each time the page opens. Reopening when nothing has been deployed still gets a 304 every time. Add max-age to index.html With Cache-Control: max-age=<seconds>, the browser uses its own copy of index.html without going to the server for the number of seconds you specify, regardless of time since modification.[^max-age] [^max-age]: RFC 9111, section 5.2.2.1. With max-age=300 (5 minutes), the previous version runs for about 5 minutes. The cost is that the window where the previous version runs doesn't go away. If the seconds you specify are longer than what the browser would have guessed, the window gets longer. Reload when loading fails When a chunk fails to load, reload the page. In Vite, a load failure is signaled with the vite:preloadError event.[^preload-error] The code below reloads only once, so that it doesn't keep reloading if the new version also fails. [^preload-error]: Load error handling in the Vite docs. window.addEventListener('vite:preloadError', () => { if (!sessionStorage.getItem('reloaded-once')) { sessionStorage.setItem('reloaded-once', '1'); window.location.reload(); } }); The window where the previous version runs doesn't change. When a user clicks the button under the previous version, the page reloads once after the chunk fails to load, and the new version runs. The cost is that in-progress page state is lost on reload. Only users who hit the error switch to the new version. Which to pick Start with no-cache. It is the only one of the three that removes the window where the previous version runs. If you want fewer round trips, use max-age instead to cap the window. Reloading on failure doesn't change the window, so combine it with one of the other two and use it to switch users who hit the error to the new version. Scope Everything above assumes that index.html has Last-Modified but no Cache-Control, that the deploy removes the previous version's files, and that the browser is Chromium. Browsers Only Chromium was measured. According to their source, Firefox and Safari also use their own copy of index.html without going to the server for 10% of the time since modification, the same as Chromium.[^other-browsers] [^other-browsers]: Firefox: nsHttpResponseHead.cpp. Safari: WebKit's CacheValidation.cpp. When the previous version's files stay In setups where the previous version's files stay on the server after a deploy, the window where the previous version runs doesn't change. Clicking the button during that window returns the previous version's chunk, and no error appears. With no error, it is harder to notice that the previous version is running. The previous version's page keeps calling the newly deployed server-side code, so that code has to stay compatible with it. A setup that puts build artifacts in S3 and runs aws s3 sync without --delete (the option that removes files that exist only at the destination) is one of these. When missing paths don't return 404 With try_files $uri /index.html (an nginx setting that returns index.html for paths that don't exist), the request for the previous version's chunk gets a 200 whose body is index.html. No 404 shows up in the access log. In the access log, look for lines where a hashed file path returned 200 with the same byte count as index.html (321 in the example below). "GET /assets/feature-DHyZGV0s.js HTTP/1.1" 200 321 "http://127.0.0.1:18024/assets/index-DB4b__oJ.js" The message that reaches the error tracker is the same Failed to fetch dynamically imported module as with the 404. With a Service Worker If a Service Worker (a script that can intercept the page's requests and answer them from its own cache) serves index.html from its own cache, the same symptom appears. In that case, adding no-cache or max-age to index.html has no effect. To check whether one is registered, run navigator.serviceWorker.getRegistrations() in the browser console. Built with webpack The same failure surfaces as ChunkLoadError. The message starts with Loading chunk <chunkId> failed.[^webpack] [^webpack]: webpack v5.111.1 source.

    4 reactions · 2 comments
  8. Reddit · · r/FirefoxCSS · u/yuuqilin · mozilla-firefox/firefox

    Gradient URL bar text with field-sizing: content in Firefox 152+ I've wanted a URL bar gradient that spans the actual URL text instead of the full input box. Firefox 152 enabled field-sizing by default, which makes this much easier to do cleanly with CSS. With field-sizing: content, #urlbar-input can shrink to the width of the displayed URL. Then background-clip: text maps the whole gradient to that width, so short and long URLs both get the full gradient instead of showing a different slice of a gradient sized to the entire URL bar. Firefox's built-in URL formatting makes it a little more complicated. The scheme and other parts outside the main domain are painted using Firefox's secondary URL text color. If browser.urlbar.formatting.enabled is disabled, a transparent text fill plus background-clip: text is basically enough. If you want to keep the native de-emphasis, Firefox still uses the hidden RGB value of the transparent text fill when painting those secondary parts. Firefox 156 also changed the secondary URL opacity from about 50% to 70%, so the compensation is different for Firefox 155 and earlier versus 156+. Translucent URL bar backgrounds such as Mica or wallpapers need a little more handling as well. The actual color-mixing math is easier to leave in the CSS comments: Full gradient URL bar implementation There is one more edge case if your CSS also moves the URL bar to the center when it opens. With a content-sized input, clicking the blank area around the text starts the click on the surrounding input container. If the URL bar moves before mouseup, the resulting click lands outside the input/container, so Firefox's normal click-to-select-all handling doesn't run. The workaround is to keep the URL bar at its toolbar position while it is :active, then move it after the click finishes: Centered popup / select-all workaround For reference, the relevant Firefox changes are: - Bug 2036620 — enable field-sizing by default - Bug 2063127 — increase the URL secondary text opacity

    5 points · 2 comments
Trending awards, every completed day and week
Gold
11st place finishes
Silver
02nd place finishes
Bronze
13rd place finishes
Awarded repositories
1Owned by mozilla-firefox now

Repositories

Ordered by gold, then silver, then bronze.

  1. firefoxBest: 1st by stars, 3,327 stars101

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. #3 by starsDaily trending
    1,514 stars
    firefox
  2. #1 by starsDaily trending
    3,327 stars
    firefox

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.