tor-commits
Threads by month
- ----- 2026 -----
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 215539 discussions
[Git][tpo/applications/mullvad-browser-update-responses][main] 4 commits: release: new version, 15.0.19 (linux-x86_64)
by ma1 (@ma1) 21 Jul '26
by ma1 (@ma1) 21 Jul '26
21 Jul '26
ma1 pushed to branch main at The Tor Project / Applications / mullvad-browser-update-responses
Commits:
8e266055 by hackademix at 2026-07-21T00:53:10+02:00
release: new version, 15.0.19 (linux-x86_64)
- - - - -
9e7345f6 by hackademix at 2026-07-21T00:53:10+02:00
release: new version, 15.0.19 (macos)
- - - - -
987afe98 by hackademix at 2026-07-21T00:53:11+02:00
release: new version, 15.0.19 (windows-x86_64)
- - - - -
065ed0ad by hackademix at 2026-07-21T00:53:11+02:00
release: new version, 15.0.19
- - - - -
31 changed files:
- update_1/release/download-linux-x86_64.json
- update_1/release/download-macos.json
- update_1/release/download-windows-x86_64.json
- update_1/release/downloads.json
- update_1/release/linux-x86_64/.htaccess
- − update_1/release/linux-x86_64/update-15.0.12-15.0.18-linux-x86_64.xml
- − update_1/release/linux-x86_64/update-15.0.14-15.0.18-linux-x86_64.xml
- + update_1/release/linux-x86_64/update-15.0.14-15.0.19-linux-x86_64.xml
- − update_1/release/linux-x86_64/update-15.0.16-15.0.18-linux-x86_64.xml
- + update_1/release/linux-x86_64/update-15.0.16-15.0.19-linux-x86_64.xml
- + update_1/release/linux-x86_64/update-15.0.18-15.0.19-linux-x86_64.xml
- − update_1/release/linux-x86_64/update-15.0.18-linux-x86_64.xml
- + update_1/release/linux-x86_64/update-15.0.19-linux-x86_64.xml
- update_1/release/macos/.htaccess
- − update_1/release/macos/update-15.0.12-15.0.18-macos.xml
- − update_1/release/macos/update-15.0.14-15.0.18-macos.xml
- + update_1/release/macos/update-15.0.14-15.0.19-macos.xml
- − update_1/release/macos/update-15.0.16-15.0.18-macos.xml
- + update_1/release/macos/update-15.0.16-15.0.19-macos.xml
- + update_1/release/macos/update-15.0.18-15.0.19-macos.xml
- − update_1/release/macos/update-15.0.18-macos.xml
- + update_1/release/macos/update-15.0.19-macos.xml
- update_1/release/windows-x86_64/.htaccess
- − update_1/release/windows-x86_64/update-15.0.12-15.0.18-windows-x86_64.xml
- − update_1/release/windows-x86_64/update-15.0.14-15.0.18-windows-x86_64.xml
- + update_1/release/windows-x86_64/update-15.0.14-15.0.19-windows-x86_64.xml
- − update_1/release/windows-x86_64/update-15.0.16-15.0.18-windows-x86_64.xml
- + update_1/release/windows-x86_64/update-15.0.16-15.0.19-windows-x86_64.xml
- + update_1/release/windows-x86_64/update-15.0.18-15.0.19-windows-x86_64.xml
- − update_1/release/windows-x86_64/update-15.0.18-windows-x86_64.xml
- + update_1/release/windows-x86_64/update-15.0.19-windows-x86_64.xml
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser-update-respo…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser-update-respo…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser-build] Pushed new tag mb-15.0.19-build1
by Pier Angelo Vendrame (@pierov) 20 Jul '26
by Pier Angelo Vendrame (@pierov) 20 Jul '26
20 Jul '26
Pier Angelo Vendrame pushed new tag mb-15.0.19-build1 at The Tor Project / Applications / tor-browser-build
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/tree/mb-…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser-build] Pushed new tag tbb-15.0.19-build1
by Pier Angelo Vendrame (@pierov) 20 Jul '26
by Pier Angelo Vendrame (@pierov) 20 Jul '26
20 Jul '26
Pier Angelo Vendrame pushed new tag tbb-15.0.19-build1 at The Tor Project / Applications / tor-browser-build
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/tree/tbb…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser-build][maint-15.0] Bug 41836&41837: Prepare Tor, Mullvad Browser 15.0.19
by Pier Angelo Vendrame (@pierov) 20 Jul '26
by Pier Angelo Vendrame (@pierov) 20 Jul '26
20 Jul '26
Pier Angelo Vendrame pushed to branch maint-15.0 at The Tor Project / Applications / tor-browser-build
Commits:
506476e1 by Pier Angelo Vendrame at 2026-07-20T17:26:30+02:00
Bug 41836&41837: Prepare Tor, Mullvad Browser 15.0.19
- - - - -
7 changed files:
- projects/browser/Bundle-Data/Docs-MB/ChangeLog.txt
- projects/browser/Bundle-Data/Docs-TBB/ChangeLog.txt
- projects/browser/config
- projects/firefox/config
- projects/geckoview/config
- projects/translation/config
- rbm.conf
Changes:
=====================================
projects/browser/Bundle-Data/Docs-MB/ChangeLog.txt
=====================================
@@ -1,3 +1,15 @@
+Mullvad Browser 15.0.19 - July 21 2026
+ * All Platforms
+ * Updated Firefox to 140.13.0esr
+ * Updated NoScript to 13.6.31.1984
+ * Bug 558: Rebase Mullvad Browser stable onto 140.13.0esr [mullvad-browser]
+ * Bug 45124: Backport Security Fixes from Firefox 153 [tor-browser]
+ * Build System
+ * All Platforms
+ * Bug 41838: Update personal_access_tokens URL in tools/fetch_changelogs.py [tor-browser-build]
+ * Windows
+ * Bug 41819: Fix windows-rs URL in projects/firefox/config [tor-browser-build]
+
Mullvad Browser 15.0.18 - July 14 2026
* All Platforms
* Updated NoScript to 13.6.30.1984
=====================================
projects/browser/Bundle-Data/Docs-TBB/ChangeLog.txt
=====================================
@@ -1,3 +1,19 @@
+Tor Browser 15.0.19 - July 21 2026
+ * All Platforms
+ * Updated NoScript to 13.6.31.1984
+ * Bug 44748: Revert Funding the Commons Implementations [tor-browser]
+ * Bug 45117: Rebase Tor Browser stable onto 140.13.0esr [tor-browser]
+ * Bug 45124: Backport Security Fixes from Firefox 153 [tor-browser]
+ * Windows + macOS + Linux
+ * Updated Firefox to 140.13.0esr
+ * Android
+ * Updated GeckoView to 140.13.0esr
+ * Build System
+ * All Platforms
+ * Bug 41838: Update personal_access_tokens URL in tools/fetch_changelogs.py [tor-browser-build]
+ * Windows
+ * Bug 41819: Fix windows-rs URL in projects/firefox/config [tor-browser-build]
+
Tor Browser 15.0.18 - July 14 2026
* All Platforms
* Updated NoScript to 13.6.30.1984
=====================================
projects/browser/config
=====================================
@@ -114,12 +114,12 @@ input_files:
- filename: dmg-root
enable: '[% ! c("var/android") %]'
- name: fenix-nightly-apk
- URL: https://ftp.mozilla.org/pub/fenix/nightly/2026/07/2026-07-13-20-26-34-fenix…
+ URL: https://ftp.mozilla.org/pub/fenix/nightly/2026/07/2026-07-19-21-13-19-fenix…
enable: '[% c("var/android") %]'
- sha256sum: 1a0ca51024fa7624c407cfa9c0ca6571878d8558fe80c5e37fbba2f687219c7e
- - URL: https://dist.torproject.org/torbrowser/noscript/noscript-13.6.30.1984.xpi
+ sha256sum: 022c3902342f38a3c83c0ab20067decaae6c9488dd824fda2259aba2b4997195
+ - URL: https://dist.torproject.org/torbrowser/noscript/noscript-13.6.31.1984.xpi
name: noscript
- sha256sum: c93285f97d9c2755d4778eb190f3f9263270554cd762f420eca5a7ff67633d0a
+ sha256sum: 28e1689956b610f328c582ae45b326cf666d909c468a87f01d48a10e256dad1f
- URL: https://addons.mozilla.org/firefox/downloads/file/4888680/ublock_origin-1.7…
name: ublock-origin
sha256sum: 40c315b0da7871868155ecfae7a50a58dfa0920aebd865e008214986f1b7c578
=====================================
projects/firefox/config
=====================================
@@ -18,12 +18,12 @@ container:
use_container: 1
var:
- firefox_platform_version: '140.12.0'
+ firefox_platform_version: '140.13.0'
firefox_version: '[% c("var/firefox_platform_version") %]esr'
browser_series: '15.0'
browser_rebase: 1
browser_branch: '[% c("var/browser_series") %]-[% c("var/browser_rebase") %]'
- browser_build: 3
+ browser_build: 2
copyright_year: '[% exec("git show -s --format=%ci " _ c("git_hash") _ "^{commit}", { exec_noco => 1 }).remove("-.*") %]'
nightly_updates_publish_dir: '[% c("var/nightly_updates_publish_dir_prefix") %]nightly-[% c("var/osname") %]'
gitlab_project: https://gitlab.torproject.org/tpo/applications/tor-browser
=====================================
projects/geckoview/config
=====================================
@@ -20,12 +20,12 @@ container:
build_apk: 1
var:
- firefox_platform_version: '140.12.0'
+ firefox_platform_version: '140.13.0'
geckoview_version: '[% c("var/firefox_platform_version") %]esr'
browser_series: '15.0'
browser_rebase: 1
browser_branch: '[% c("var/browser_series") %]-[% c("var/browser_rebase") %]'
- browser_build: 3
+ browser_build: 2
gitlab_project: https://gitlab.torproject.org/tpo/applications/tor-browser
git_commit: '[% exec("git rev-parse " _ c("git_hash") _ "^{commit}", { exec_noco => 1 }) %]'
deps:
=====================================
projects/translation/config
=====================================
@@ -18,7 +18,7 @@ steps:
git_hash: 'base-browser'
tor-browser:
tor-browser: '[% INCLUDE build %]'
- git_hash: ca0d23d9a380a2bce5a677952789738a57d8f715
+ git_hash: 510b52a6963331b5c67471921ed5c54a77370840
targets:
nightly:
git_hash: 'tor-browser'
=====================================
rbm.conf
=====================================
@@ -74,11 +74,11 @@ buildconf:
git_signtag_opt: '-s'
var:
- torbrowser_version: '15.0.18'
+ torbrowser_version: '15.0.19'
torbrowser_build: 'build1'
# This should be the date of when the build is started. For the build
# to be reproducible, browser_release_date should always be in the past.
- browser_release_date: '2026/07/14 06:29:51'
+ browser_release_date: '2026/07/20 08:00:00'
browser_release_date_timestamp: '[% USE date; date.format(c("var/browser_release_date"), "%s") %]'
browser_default_channel: release
browser_platforms:
@@ -128,11 +128,10 @@ var:
updater_enabled: 1
build_mar: 1
torbrowser_incremental_from:
+ - 15.0.18
- '[% IF c("var/tor-browser") %]15.0.17[% END %]'
- 15.0.16
- - '[% IF c("var/tor-browser") %]15.0.15[% END %]'
- '[% IF c("var/mullvad-browser") %]15.0.14[% END %]'
- - '[% IF c("var/mullvad-browser") %]15.0.12[% END %]'
mar_channel_id: '[% c("var/projectname") %]-torproject-[% c("var/channel") %]'
# By default, we sort the list of installed packages. This allows sharing
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/5…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/5…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser] Pushed new tag mullvad-browser-140.13.0esr-15.0-1-build2
by ma1 (@ma1) 20 Jul '26
by ma1 (@ma1) 20 Jul '26
20 Jul '26
ma1 pushed new tag mullvad-browser-140.13.0esr-15.0-1-build2 at The Tor Project / Applications / Mullvad Browser
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/tree/mullv…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-140.13.0esr-15.0-1] 15 commits: Bug 1903144 - Refactors readerview to use setHTML...
by ma1 (@ma1) 20 Jul '26
by ma1 (@ma1) 20 Jul '26
20 Jul '26
ma1 pushed to branch mullvad-browser-140.13.0esr-15.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
2b655f24 by Jeff Boek at 2026-07-20T19:09:33+02:00
Bug 1903144 - Refactors readerview to use setHTML r=geckoview-reviewers,android-reviewers,frontend-codestyle-reviewers,nalexander,freddyb
Differential Revision: https://phabricator.services.mozilla.com/D297199
- - - - -
8bde7d7c by Gela at 2026-07-20T19:09:33+02:00
Bug 2021964 - Clipboard in toolbar r=android-reviewers,ohall
Differential Revision: https://phabricator.services.mozilla.com/D287466
Additional changes per https://gitlab.torproject.org/ma1/tor-browser-confidential/-/merge_requests…
- - - - -
8955ffc7 by Randell Jesup at 2026-07-20T19:09:34+02:00
Bug 2036906: Fix CacheEntry::mDoomCallback data race r=necko-reviewers,valentin
Differential Revision: https://phabricator.services.mozilla.com/D298396
- - - - -
0151652b by Pier Angelo Vendrame at 2026-07-20T19:09:35+02:00
Bug 2038357 - Consider zoom when spoofing img srcset scaling. r=tjr
Ensure RFP-spoofed srcset scaling accounts for page zoom so that its
behavior matches the non-spoofed case.
Differential Revision: https://phabricator.services.mozilla.com/D300259
- - - - -
eafe5cfb by David Parks at 2026-07-20T19:09:35+02:00
Bug 2038868: Set Zone.Identifier for dragged URLs r=win-reviewers,gstoll
An (AFAICT undocumented) long-standing feature in Windows is the ability to
set the Zone.Identifier ADS on a file by writing to a file named
"${filename}:Zone.Identifier". In DND, this patch exports this alongside
the ${filename} that will be used for the shortcut, so explorer writes both.
This causes the UAC to kick in when the shortcut is launched.
Differential Revision: https://phabricator.services.mozilla.com/D301935
- - - - -
59ab9ec8 by Emilio Cobos Álvarez at 2026-07-20T19:09:36+02:00
Bug 2045394 - Fix nsFrameSetFrame primary frame handling of children. r=layout-reviewers,dshin
As always, frameset still sucks.
Differential Revision: https://phabricator.services.mozilla.com/D305125
- - - - -
83ec3f76 by Emilio Cobos Álvarez at 2026-07-20T19:09:36+02:00
Bug 2045606 - Don't skip layout for fragmented blocks. a=pascalc DONTBUILD
This is the low-risk fix, but ideally we just pull the children up.
For flex / grid normalize child lists before skipping layout.
Original Revision: https://phabricator.services.mozilla.com/D306764
Differential Revision: https://phabricator.services.mozilla.com/D307724
- - - - -
f47621b9 by John Lin at 2026-07-20T19:09:37+02:00
Bug 2023515 - p1: add 'copy between native memory' method to SampleBuffer. a=diannaS DONTBUILD
With this method, C++ code won't have to create a temporary Java object
just to copy sample buffer data into native heap.
Original Revision: https://phabricator.services.mozilla.com/D289046
Differential Revision: https://phabricator.services.mozilla.com/D292302
- - - - -
e2f757a8 by John Lin at 2026-07-20T19:09:37+02:00
Bug 2023515 - p2: use SampleBuffer native copy method. a=diannaS DONTBUILD
Original Revision: https://phabricator.services.mozilla.com/D289047
Differential Revision: https://phabricator.services.mozilla.com/D292303
- - - - -
4d0e96da by John Lin at 2026-07-20T19:09:38+02:00
Bug 2030198 - report error when fails to copy Android encoder output data. a=diannaS DONTBUILD
Besides reporting error, drain state should also be updated and promise should be rejected when needed.
Original Revision: https://phabricator.services.mozilla.com/D293708
Differential Revision: https://phabricator.services.mozilla.com/D294459
- - - - -
1288af7a by John Lin at 2026-07-20T19:09:38+02:00
Bug 2047802 - validate SampleBuffer I/O bounds. a=pascalc
Original Revision: https://phabricator.services.mozilla.com/D307893
Differential Revision: https://phabricator.services.mozilla.com/D310670
- - - - -
b7cb1cdb by Jamie Nicol at 2026-07-20T19:09:39+02:00
Bug 2048345 - Make RemoteSurfaceAllocator ensure that surfaces are owned by the calling client. a=pascalc
Original Revision: https://phabricator.services.mozilla.com/D308282
Differential Revision: https://phabricator.services.mozilla.com/D308986
- - - - -
20338498 by Gabriele Svelto at 2026-07-20T19:09:40+02:00
Bug 2050534 - Use only types that have the same C & Rust representation in the crash annotations a=RyanVM,pascalc
Differential Revision: https://phabricator.services.mozilla.com/D311897
- - - - -
9643c8ca by Marcin Koziński at 2026-07-20T19:09:41+02:00
Bug 2040834 - Add an initial delay before enabling positive button in addon permissions dialog r=android-reviewers,android-addons-reviewers,twhite,robwu
Differential Revision: https://phabricator.services.mozilla.com/D302742
- - - - -
2a58a489 by Mugurell at 2026-07-20T19:09:42+02:00
Bug 2047608 - Ensure dialogs are visible a=pascalc
Original Revision: https://phabricator.services.mozilla.com/D306989
Differential Revision: https://phabricator.services.mozilla.com/D310044
- - - - -
41 changed files:
- dom/base/ResponsiveImageSelector.cpp
- dom/media/platforms/android/AndroidDataEncoder.cpp
- dom/media/platforms/android/RemoteDataDecoder.cpp
- layout/generic/nsBlockFrame.cpp
- layout/generic/nsFlexContainerFrame.cpp
- layout/generic/nsFrameSetFrame.cpp
- layout/generic/nsGridContainerFrame.cpp
- mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/AccountEvent.kt
- mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/ui/AddonDialogFragment.kt
- mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/ui/PermissionsDialogFragment.kt
- mobile/android/android-components/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/PermissionsDialogFragmentTest.kt
- mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/PromptDialogFragment.kt
- mobile/android/android-components/components/feature/readerview/src/main/assets/extensions/readerview/readerview.js
- mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt
- + mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/NoObscuredTouchesDialogFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/ShareController.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/ToolbarPopupWindow.kt
- mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IChildProcess.aidl
- mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IProcessManager.aidl
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RemoteSurfaceAllocator.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SampleBuffer.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceGpuProcess.java
- mozglue/android/SharedMemNatives.cpp
- netwerk/cache2/CacheEntry.cpp
- netwerk/cache2/CacheEntry.h
- + testing/web-platform/tests/css/css-multicol/content-visibility-001-crash.html
- toolkit/components/resistfingerprinting/tests/browser/browser.toml
- + toolkit/components/resistfingerprinting/tests/browser/browser_srcset.js
- + toolkit/components/resistfingerprinting/tests/browser/srcset.html
- toolkit/crashreporter/crash_helper_server/src/crash_generation.rs
- toolkit/crashreporter/crash_helper_server/src/phc.rs
- toolkit/crashreporter/mozannotation_client/src/lib.rs
- toolkit/crashreporter/mozannotation_server/src/errors.rs
- toolkit/crashreporter/mozannotation_server/src/lib.rs
- toolkit/crashreporter/nsExceptionHandler.cpp
- widget/windows/nsDataObj.cpp
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/80…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/80…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser] Pushed new tag tor-browser-140.13.0esr-15.0-1-build2
by ma1 (@ma1) 20 Jul '26
by ma1 (@ma1) 20 Jul '26
20 Jul '26
ma1 pushed new tag tor-browser-140.13.0esr-15.0-1-build2 at The Tor Project / Applications / Tor Browser
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/tree/tor-brows…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-140.13.0esr-15.0-1] 15 commits: Bug 1903144 - Refactors readerview to use setHTML...
by ma1 (@ma1) 20 Jul '26
by ma1 (@ma1) 20 Jul '26
20 Jul '26
ma1 pushed to branch tor-browser-140.13.0esr-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
059b1214 by Jeff Boek at 2026-07-20T10:17:34+02:00
Bug 1903144 - Refactors readerview to use setHTML r=geckoview-reviewers,android-reviewers,frontend-codestyle-reviewers,nalexander,freddyb
Differential Revision: https://phabricator.services.mozilla.com/D297199
- - - - -
743fa6e4 by Gela at 2026-07-20T16:15:50+02:00
Bug 2021964 - Clipboard in toolbar r=android-reviewers,ohall
Differential Revision: https://phabricator.services.mozilla.com/D287466
Additional changes per https://gitlab.torproject.org/ma1/tor-browser-confidential/-/merge_requests…
- - - - -
51cf6e7f by Randell Jesup at 2026-07-20T16:16:45+02:00
Bug 2036906: Fix CacheEntry::mDoomCallback data race r=necko-reviewers,valentin
Differential Revision: https://phabricator.services.mozilla.com/D298396
- - - - -
6336f7ba by Pier Angelo Vendrame at 2026-07-20T16:16:46+02:00
Bug 2038357 - Consider zoom when spoofing img srcset scaling. r=tjr
Ensure RFP-spoofed srcset scaling accounts for page zoom so that its
behavior matches the non-spoofed case.
Differential Revision: https://phabricator.services.mozilla.com/D300259
- - - - -
e3e137d0 by David Parks at 2026-07-20T16:16:47+02:00
Bug 2038868: Set Zone.Identifier for dragged URLs r=win-reviewers,gstoll
An (AFAICT undocumented) long-standing feature in Windows is the ability to
set the Zone.Identifier ADS on a file by writing to a file named
"${filename}:Zone.Identifier". In DND, this patch exports this alongside
the ${filename} that will be used for the shortcut, so explorer writes both.
This causes the UAC to kick in when the shortcut is launched.
Differential Revision: https://phabricator.services.mozilla.com/D301935
- - - - -
b959a318 by Emilio Cobos Álvarez at 2026-07-20T16:16:48+02:00
Bug 2045394 - Fix nsFrameSetFrame primary frame handling of children. r=layout-reviewers,dshin
As always, frameset still sucks.
Differential Revision: https://phabricator.services.mozilla.com/D305125
- - - - -
9f82444d by Emilio Cobos Álvarez at 2026-07-20T16:16:49+02:00
Bug 2045606 - Don't skip layout for fragmented blocks. a=pascalc DONTBUILD
This is the low-risk fix, but ideally we just pull the children up.
For flex / grid normalize child lists before skipping layout.
Original Revision: https://phabricator.services.mozilla.com/D306764
Differential Revision: https://phabricator.services.mozilla.com/D307724
- - - - -
dd5aaf2e by John Lin at 2026-07-20T16:16:50+02:00
Bug 2023515 - p1: add 'copy between native memory' method to SampleBuffer. a=diannaS DONTBUILD
With this method, C++ code won't have to create a temporary Java object
just to copy sample buffer data into native heap.
Original Revision: https://phabricator.services.mozilla.com/D289046
Differential Revision: https://phabricator.services.mozilla.com/D292302
- - - - -
cf906219 by John Lin at 2026-07-20T16:16:51+02:00
Bug 2023515 - p2: use SampleBuffer native copy method. a=diannaS DONTBUILD
Original Revision: https://phabricator.services.mozilla.com/D289047
Differential Revision: https://phabricator.services.mozilla.com/D292303
- - - - -
43601439 by John Lin at 2026-07-20T16:16:52+02:00
Bug 2030198 - report error when fails to copy Android encoder output data. a=diannaS DONTBUILD
Besides reporting error, drain state should also be updated and promise should be rejected when needed.
Original Revision: https://phabricator.services.mozilla.com/D293708
Differential Revision: https://phabricator.services.mozilla.com/D294459
- - - - -
3922d26b by John Lin at 2026-07-20T16:16:53+02:00
Bug 2047802 - validate SampleBuffer I/O bounds. a=pascalc
Original Revision: https://phabricator.services.mozilla.com/D307893
Differential Revision: https://phabricator.services.mozilla.com/D310670
- - - - -
9c9ae74b by Jamie Nicol at 2026-07-20T16:16:54+02:00
Bug 2048345 - Make RemoteSurfaceAllocator ensure that surfaces are owned by the calling client. a=pascalc
Original Revision: https://phabricator.services.mozilla.com/D308282
Differential Revision: https://phabricator.services.mozilla.com/D308986
- - - - -
b57757a4 by Gabriele Svelto at 2026-07-20T16:16:55+02:00
Bug 2050534 - Use only types that have the same C & Rust representation in the crash annotations a=RyanVM,pascalc
Differential Revision: https://phabricator.services.mozilla.com/D311897
- - - - -
22bdec1c by Marcin Koziński at 2026-07-20T16:16:56+02:00
Bug 2040834 - Add an initial delay before enabling positive button in addon permissions dialog r=android-reviewers,android-addons-reviewers,twhite,robwu
Differential Revision: https://phabricator.services.mozilla.com/D302742
- - - - -
b98a4b65 by Mugurell at 2026-07-20T16:16:57+02:00
Bug 2047608 - Ensure dialogs are visible a=pascalc
Original Revision: https://phabricator.services.mozilla.com/D306989
Differential Revision: https://phabricator.services.mozilla.com/D310044
- - - - -
41 changed files:
- dom/base/ResponsiveImageSelector.cpp
- dom/media/platforms/android/AndroidDataEncoder.cpp
- dom/media/platforms/android/RemoteDataDecoder.cpp
- layout/generic/nsBlockFrame.cpp
- layout/generic/nsFlexContainerFrame.cpp
- layout/generic/nsFrameSetFrame.cpp
- layout/generic/nsGridContainerFrame.cpp
- mobile/android/android-components/components/concept/sync/src/main/java/mozilla/components/concept/sync/AccountEvent.kt
- mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/ui/AddonDialogFragment.kt
- mobile/android/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/ui/PermissionsDialogFragment.kt
- mobile/android/android-components/components/feature/addons/src/test/java/mozilla/components/feature/addons/ui/PermissionsDialogFragmentTest.kt
- mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/PromptDialogFragment.kt
- mobile/android/android-components/components/feature/readerview/src/main/assets/extensions/readerview/readerview.js
- mobile/android/android-components/components/feature/sitepermissions/src/main/java/mozilla/components/feature/sitepermissions/SitePermissionsDialogFragment.kt
- + mobile/android/android-components/components/support/base/src/main/java/mozilla/components/support/base/android/NoObscuredTouchesDialogFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/ShareController.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/share/ShareFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/ToolbarPopupWindow.kt
- mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IChildProcess.aidl
- mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/process/IProcessManager.aidl
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RemoteSurfaceAllocator.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/SampleBuffer.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceChildProcess.java
- mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoServiceGpuProcess.java
- mozglue/android/SharedMemNatives.cpp
- netwerk/cache2/CacheEntry.cpp
- netwerk/cache2/CacheEntry.h
- + testing/web-platform/tests/css/css-multicol/content-visibility-001-crash.html
- toolkit/components/resistfingerprinting/tests/browser/browser.toml
- + toolkit/components/resistfingerprinting/tests/browser/browser_srcset.js
- + toolkit/components/resistfingerprinting/tests/browser/srcset.html
- toolkit/crashreporter/crash_helper_server/src/crash_generation.rs
- toolkit/crashreporter/crash_helper_server/src/phc.rs
- toolkit/crashreporter/mozannotation_client/src/lib.rs
- toolkit/crashreporter/mozannotation_server/src/errors.rs
- toolkit/crashreporter/mozannotation_server/src/lib.rs
- toolkit/crashreporter/nsExceptionHandler.cpp
- widget/windows/nsDataObj.cpp
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/bdf010…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/bdf010…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-153.0a1-16.0-2] fixup! Tweaks to the build system
by brizental (@brizental) 20 Jul '26
by brizental (@brizental) 20 Jul '26
20 Jul '26
brizental pushed to branch mullvad-browser-153.0a1-16.0-2 at The Tor Project / Applications / Mullvad Browser
Commits:
7502227d by Beatriz Rizental at 2026-07-20T13:01:30-03:00
fixup! Tweaks to the build system
Bug 45108: Restore MOZ_PKG_MAC_DSSTORE/BACKGROUND/ICON env var names for DMG packaging
Got the env vars names wrong when addressing a rebase conflict. Oops.
- - - - -
1 changed file:
- toolkit/moz.configure
Changes:
=====================================
toolkit/moz.configure
=====================================
@@ -3960,9 +3960,9 @@ include("../build/moz.configure/update-programs.configure")
# Per-format extra args passed to mozbuild.action.package
# ==============================================================
-option(env="MOZ_DMG_DSSTORE", nargs=1, help="Path to the .dsstore file for DMG packaging")
-option(env="MOZ_DMG_BACKGROUND", nargs=1, help="Path to the background image for DMG packaging")
-option(env="MOZ_DMG_ICON", nargs=1, help="Path to the disk icon for DMG packaging")
+option(env="MOZ_PKG_MAC_DSSTORE", nargs=1, help="Path to the .dsstore file for DMG packaging")
+option(env="MOZ_PKG_MAC_BACKGROUND", nargs=1, help="Path to the background image for DMG packaging")
+option(env="MOZ_PKG_MAC_ICON", nargs=1, help="Path to the disk icon for DMG packaging")
@depends(
moz_pkg_format,
@@ -3972,9 +3972,9 @@ option(env="MOZ_DMG_ICON", nargs=1, help="Path to the disk icon for DMG packagin
moz_branding_directory,
moz_app_displayname,
build_environment.topsrcdir,
- "MOZ_DMG_DSSTORE",
- "MOZ_DMG_BACKGROUND",
- "MOZ_DMG_ICON",
+ "MOZ_PKG_MAC_DSSTORE",
+ "MOZ_PKG_MAC_BACKGROUND",
+ "MOZ_PKG_MAC_ICON",
)
@imports(_from="os.path", _import="join")
@imports("shlex")
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/750…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/750…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser-build][main] Bug 41800: Add a script for the new offline manual.
by henry (@henry) 20 Jul '26
by henry (@henry) 20 Jul '26
20 Jul '26
henry pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
fee9e7b2 by Henry Wilkes at 2026-07-20T14:55:57+00:00
Bug 41800: Add a script for the new offline manual.
- - - - -
2 changed files:
- + projects/manual/package_marble_build_artifacts.py
- + projects/manual/test_package_marble_build_artifacts.py
Changes:
=====================================
projects/manual/package_marble_build_artifacts.py
=====================================
@@ -0,0 +1,669 @@
+import html
+import html.parser
+import os
+import re
+import shutil
+from urllib.parse import quote_plus, urlparse
+
+DEFAULT_LOCALE = "en"
+ORIG_HTML_PATH = "offline/tor-browser/index.html"
+
+# The '#' will be replaced with the locale name.
+HTML_NAME_TEMPLATE = "aboutManual-#.html"
+ASSETS_DIR_NAME = "assets"
+
+
+class ManualParser(html.parser.HTMLParser):
+ """
+ A parser for the Tor Browser manual. Extracts the <body> of the input HTML,
+ swaps attributes, and writes out a new HTML.
+ """
+
+ _CHROME_URI_BASE = "chrome://browser/content/aboutmanual/"
+ _CHROME_ASSET_PATH = _CHROME_URI_BASE + ASSETS_DIR_NAME + "/"
+ _CHROME_JS = _CHROME_URI_BASE + "aboutManual.js"
+
+ # The base for external support pages.
+ _SUPPORT_URI_BASE = "https://support.torproject.org"
+
+ # The name of online page that the offline manual is derived from (the
+ # `data-olm-product` value).
+ # In our case, the offline manual is derived from
+ # https://support.torproject.org/tor-browser/
+ _TOP_PAGE_NAME = "tor-browser"
+
+ # The `id` attribute value that points to the top-page.
+ _TOP_ID = "index"
+
+ # The pages that are covered by the offline manual.
+ # The first value in the tuple specifies the paths that are covered. Every
+ # path that starts with this is considered internal.
+ # The second value indicates whether an anchor reference to this page should
+ # include the start of the path or not.
+ _INTERNAL_PAGES = [
+ ([_TOP_PAGE_NAME], False),
+ (["get-in-touch", "bug-or-feedback"], True),
+ (["get-in-touch", "user-support"], True),
+ ]
+
+ # The content security policy we want for the output.
+ _CSP = "default-src 'none'; style-src chrome:; img-src chrome:; script-src chrome:"
+
+ # Elements that should not appear in the <body>.
+ _FORBIDDEN_BODY_TAGS = [
+ "script",
+ "noscript",
+ "meta",
+ "link",
+ "html",
+ "head",
+ "body",
+ ]
+
+ # HTML elements that are void elements.
+ _VOID_HTML_TAGS = [
+ "area",
+ "base",
+ "br",
+ "col",
+ "embed",
+ "hr",
+ "img",
+ "input",
+ "link",
+ "meta",
+ "source",
+ "track",
+ "wbr",
+ ]
+
+ _VALID_DIR_VALS = ["rtl", "ltr"]
+
+ def __init__(
+ self, locale: str, html_path: str, top_dir: str, all_locales: list[str]
+ ) -> None:
+ """
+ Create a new parser for the offline manual.
+ """
+ super().__init__(convert_charrefs=True)
+ self._locale = locale
+ self._html_path = html_path
+ self._html_dir = os.path.dirname(html_path)
+ self._top_dir = top_dir
+ self._all_locales = all_locales
+
+ # As we parse the HTML, we track which tags we are currently inside, as
+ # well as some other details about those tags.
+ # The last element in the list is the most recent tag.
+ self._ancestor_details: list[dict[str, str]] = []
+
+ # The <body> content we should write to the output.
+ self._body_content = ""
+
+ # Whether we are currently below the <body> tag.
+ self._in_body = False
+ # Whether we have encountered the <body> tag at some point.
+ self._visited_body = False
+ # The lang attribute found on the <html> element.
+ self._lang: None | str = None
+ # The dir attribute found on the <html> element.
+ self._dir: None | str = None
+ # The <title> content we have found.
+ self._title = ""
+ # The stylesheet references to include.
+ self._style_hrefs: list[str] = []
+ # The found asset paths.
+ self._asset_paths: dict[str, str] = {}
+ # A list of all internal references we want to verify exist.
+ # The first member of the tuple gives the document position where the
+ # `href` was found.
+ # The second member of the tuple gives the `href` value.
+ self._internal_hrefs: list[tuple[tuple[int, int], str]] = []
+ # The list of all `id` attributes we found in the <body>.
+ self._all_ids: list[str] = []
+
+ def _error(self, message: str, pos: None | tuple[int, int] = None) -> ValueError:
+ """
+ Create a new parsing error.
+
+ :param message: The message to show.
+ :param pos: The document position associated with the error. Defaults to
+ the current position.
+
+ :returns: A new error instance.
+ """
+ if pos is None:
+ pos = self.getpos()
+ return ValueError(f"{self._html_path}: {pos}: {message}")
+
+ def _in_context(self, expected_ancestors: list[str]) -> bool:
+ """
+ Test if we are currently below the given ancestors.
+
+ :param expected_ancestors: The tag names for the ancestors we expect.
+
+ :returns: Whether we are directly below the specified ancestors.
+ """
+ return [d["tag"] for d in self._ancestor_details] == expected_ancestors
+
+ def _add_to_body(self, data: str) -> None:
+ """
+ Write to the body content.
+
+ :param data: The content to write.
+ """
+ self._body_content += data
+
+ def _add_end_tag_to_body(self, tag: str) -> None:
+ """
+ Write an ending tag to the body.
+
+ :param tag: The name of the tag to close.
+ """
+ self._add_to_body(f"</{tag}>")
+
+ def _add_start_tag_to_body(
+ self, tag: str, attrs: dict[str, None | str], self_close: bool
+ ) -> None:
+ """
+ Write a starting tag to the body.
+
+ :param tag: The name of the tag to open.
+ :param attrs: The attributes for this tag.
+ :param self_close: Whether this tag should be self-closed.
+ """
+ attr_part = ""
+ for name, value in attrs.items():
+ attr_part += f" {name}"
+ if value is not None:
+ attr_part += f'="{html.escape(value, quote=True)}"'
+ close_part = " />" if self_close else ">"
+ self._add_to_body(f"<{tag}{attr_part}{close_part}")
+
+ def _has_class(self, attrs: dict[str, None | str], class_name: str) -> bool:
+ """
+ Test if an element has a certain class.
+
+ :param attrs: The attributes for the element.
+ :param class_name: The name of the class to check for.
+
+ :returns: Whether the element has the given class.
+ """
+ class_val = attrs.get("class")
+ if not class_val:
+ return False
+ return class_name in class_val.split(" ")
+
+ def _handle_html_tag(self, attrs: dict[str, None | str]) -> None:
+ """
+ Process a html tag.
+
+ :param attrs: The tag's attributes.
+ """
+ if not self._in_context([]):
+ raise self._error("html has a parent")
+ if self._lang:
+ raise self._error("More than one html tag")
+ lang = attrs.get("lang")
+ if not lang:
+ raise self._error("html is missing a lang attribute")
+ if lang != self._locale:
+ raise self._error(f"Unexpected lang attribute: {lang}")
+ dir_val = attrs.get("dir")
+ if not dir_val:
+ raise self._error("html is missing a dir attribute")
+ if dir_val not in self._VALID_DIR_VALS:
+ raise self._error(f"Unexpected dir attribute: {dir_val}")
+
+ self._lang = lang
+ self._dir = dir_val
+
+ _NON_SAFE_ASSET_CHARS = re.compile(r"[^a-zA-Z0-9_.-]")
+
+ def _swap_asset_path(self, orig_path: str) -> str:
+ """
+ Swap an an asset path with a new one, and record the old path.
+
+ :param orig_path: The original path for the asset.
+
+ :returns: The new path to use.
+ """
+ # Parse as a URL so we can strip any queries.
+ url = urlparse(orig_path)
+ if url.scheme:
+ raise self._error(f"Unexpected asset path with a scheme: {url.scheme}")
+
+ if url.path.startswith("/"):
+ abs_path = os.path.abspath(os.path.join(self._top_dir, url.path[1:]))
+ else:
+ abs_path = os.path.abspath(os.path.join(self._html_dir, url.path))
+
+ if os.path.commonpath([self._top_dir, abs_path]) != self._top_dir:
+ raise self._error(f"References an asset outside {self._top_dir}")
+ if not os.path.isfile(abs_path):
+ raise self._error(f"References a non-existent asset: {abs_path}")
+
+ # Replace any (unexpected) non-safe characters with underscores.
+ asset_name = self._NON_SAFE_ASSET_CHARS.sub(
+ "_", os.path.relpath(abs_path, start=self._top_dir).replace("/", "__")
+ )
+
+ if asset_name not in self._asset_paths:
+ self._asset_paths[asset_name] = abs_path
+ elif self._asset_paths[asset_name] != abs_path:
+ raise self._error(f"More than one asset with the same name: {asset_name}")
+
+ return self._CHROME_ASSET_PATH + asset_name
+
+ def _handle_link_tag(self, attrs: dict[str, None | str]) -> None:
+ """
+ Process a link tag.
+
+ :param attrs: The tag's attributes.
+ """
+ if attrs.get("rel") != "stylesheet":
+ return
+
+ href = attrs.get("href")
+ if not href:
+ raise self._error("stylesheet link missing an href")
+
+ self._style_hrefs.append(self._swap_asset_path(href))
+
+ def _handle_body_tag(self) -> None:
+ """
+ Process a body tag.
+ """
+ if not self._in_context(["html"]):
+ raise self._error("Wrong context for the body tag")
+ if self._visited_body:
+ raise self._error("More than one body tag")
+ self._visited_body = True
+
+ def _handle_img_tag(self, attrs: dict[str, None | str]) -> None:
+ """
+ Process an img tag.
+
+ :param attrs: The tag's attributes, which may be modified.
+ """
+ if "srcset" in attrs:
+ # In principle, we should also map "srcset" as we map "src", but it
+ # is unexpected and not worth the parsing logic.
+ raise self._error("Unhandled srcset attribute")
+ src = attrs.get("src")
+ if not src:
+ return
+
+ attrs["src"] = self._swap_asset_path(src)
+
+ def _convert_relative_href(self, href: str) -> tuple[str, bool]:
+ """
+ Convert a relative href into a href that can be used in the final HTML.
+
+ :param href: The href to convert.
+
+ :returns: The new href, and whether the href is an internal link.
+ """
+ url = urlparse(href)
+ parts = [p for p in url.path.replace("../../", "").split("/") if p]
+ if not parts or ".." in parts or "." in parts:
+ raise self._error(f"Unexpected path: {href}")
+
+ if parts == [self._TOP_PAGE_NAME] and not url.fragment:
+ href = "#" + self._TOP_ID
+ return href, True
+
+ for page_path, keep_prefix in self._INTERNAL_PAGES:
+ cmp_len = len(page_path)
+ if parts[:cmp_len] == page_path:
+ if not keep_prefix:
+ parts = parts[cmp_len:]
+ href = "#" + "__".join(parts)
+ if url.fragment:
+ href += "___" + url.fragment
+ return href, True
+
+ href = self._SUPPORT_URI_BASE + "/"
+ if self._locale != DEFAULT_LOCALE:
+ href += quote_plus(self._locale) + "/"
+ href += "/".join(parts)
+ if url.fragment:
+ href += "#" + url.fragment
+ # Note: we don't expect a query parameter.
+ return href, False
+
+ def _handle_a_tag(
+ self, attrs: dict[str, str | None], details: dict[str, str]
+ ) -> None:
+ """
+ Process an anchor tag.
+
+ :param attrs: The tag's attributes, which may be modified.
+ :param details: The tag details to save, which may be modified.
+ """
+ # Always remove these attributes, and maybe replace them below.
+ # NOTE: Whilst, some "rel" attribute tokens would, in principle, be
+ # valid to keep, we don't expect it to be used in the origin for
+ # anything other than "noopener" or "norefferor" in the original
+ # document.
+ for attr_name in ("rel", "referrerpolicy", "target"):
+ if attr_name in attrs:
+ del attrs[attr_name]
+
+ # Change href if it includes relative path
+ href = attrs.get("href")
+ if not href:
+ return
+
+ is_external = False
+ is_internal = False
+ if href.startswith("http:") or href.startswith("https:"):
+ is_external = True
+ elif href.startswith("mailto:"):
+ # Do not allow mailto:
+ del attrs["href"]
+ elif href.startswith("#"):
+ # Keep as is.
+ is_internal = True
+ elif href.startswith("../../"):
+ href, is_internal = self._convert_relative_href(href)
+ attrs["href"] = href
+ if not is_internal:
+ is_external = True
+ else:
+ raise self._error(f"Unexpected href: {href}")
+
+ if is_external:
+ attrs["target"] = "_blank" # Implies rel="noopener".
+ # Is noreferrer needed for external links within a chrome: document?
+ attrs["rel"] = "noreferrer"
+ if is_internal:
+ self._internal_hrefs.append((self.getpos(), href))
+
+ return
+
+ def _handle_div_tag(
+ self, attrs: dict[str, str | None], details: dict[str, str]
+ ) -> None:
+ """
+ Process a div tag.
+
+ :param attrs: The tag's attributes, which may be modified.
+ :param details: The tag details to save, which may be modified.
+ """
+ if self._has_class(attrs, "heading-anchor"):
+ # Convert the .heading-anchor id to include a prefix from its
+ # ancestor .olm-page element.
+ el_id = attrs.get("id")
+ if not el_id:
+ raise self._error("Heading is missing an id")
+ page_id = None
+ # Search for the nearest ancestor with a page-id.
+ for ancestor in reversed(self._ancestor_details):
+ page_id = ancestor.get("page-id")
+ if page_id:
+ break
+ if not page_id:
+ raise self._error("Missing a page to use for the heading id")
+ attrs["id"] = page_id + "___" + el_id
+ elif self._has_class(attrs, "olm-page"):
+ el_id = attrs.get("id")
+ if not el_id:
+ raise self._error("olm-page is missing an id")
+ for other in self._ancestor_details:
+ if "page-id" in other:
+ raise self._error("olm-page is below another")
+ details["page-id"] = el_id
+
+ def _save_id(self, tag: str, attrs: dict[str, str | None]) -> None:
+ """
+ Maybe save an `id` to the list of `id`s that can be referenced
+ internally.
+ """
+ attr_id = attrs.get("id")
+ if not attr_id or not self._in_body:
+ # Ignore
+ return
+ if attr_id in self._all_ids:
+ if tag == "input" and self._has_class(attrs, "toggler"):
+ # Known issue that *some* <input class="toggler" /> will use
+ # the same id as the heading element above it.
+ # TODO: tor-browser-build#41818. Remove once this element is
+ # removed.
+ del attrs["id"]
+ return
+ raise self._error(f"Duplicate id: {attr_id}")
+ self._all_ids.append(attr_id)
+
+ def _handle_tag(
+ self, tag: str, attr_pairs: list[tuple[str, str | None]], is_closed: bool
+ ) -> None:
+ """
+ Handle a start tag,
+
+ :param tag: The name of the tag.
+ :param attr_pairs: The list of all attributes for this tag.
+ :param is_closed: Whether the tag is self-closing.
+ """
+ details = {"tag": tag, "orig-tag": tag}
+
+ attrs = {}
+ for attr_name, attr_value in attr_pairs:
+ if attr_name in attrs:
+ raise self._error(f"{tag} has a duplicate attribute: {attr_name}")
+ attrs[attr_name] = attr_value
+
+ if not self._in_body:
+ if tag == "html":
+ self._handle_html_tag(attrs)
+ elif tag == "link":
+ self._handle_link_tag(attrs)
+ elif tag == "body":
+ self._handle_body_tag()
+ self._in_body = True
+ elif tag in self._FORBIDDEN_BODY_TAGS:
+ raise self._error(f"Unexpected {tag} tag in body")
+ elif tag == "img":
+ self._handle_img_tag(attrs)
+ elif tag == "a":
+ self._handle_a_tag(attrs, details)
+ elif tag == "div":
+ self._handle_div_tag(attrs, details)
+
+ # The tag may have changed.
+ tag = details["tag"]
+ self._save_id(tag, attrs)
+
+ # NOTE: Technically, we would need to take the `xmlns` into account to
+ # ensure that we are still in a HTML context, but we don't expect these
+ # void tag names to appear in other contexts.
+ is_void_tag = tag in self._VOID_HTML_TAGS
+ is_open_tag = not is_void_tag and not is_closed
+
+ if self._in_body:
+ # Write the tag, which may differ from the original tag.
+ self._add_start_tag_to_body(tag, attrs, is_void_tag)
+ if not is_void_tag and not is_open_tag:
+ # Close immediately to keep this empty.
+ self._add_end_tag_to_body(tag)
+
+ if is_open_tag:
+ # Track this as an ancestor.
+ self._ancestor_details.append(details)
+
+ ## HTMLParser API
+
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
+ self._handle_tag(tag, attrs, False)
+
+ def handle_startendtag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
+ self._handle_tag(tag, attrs, True)
+
+ def handle_endtag(self, tag: str) -> None:
+ if not self._ancestor_details:
+ raise self._error(f"Unmatched end tag: {tag}")
+ details = self._ancestor_details.pop()
+ if details["orig-tag"] != tag:
+ raise self._error(f"Unmatched end tag: {tag}")
+
+ if self._in_body:
+ # Write the tag found in `details` to match the tag that was *written*
+ # for the start tag, which may differ from `orig-tag`.
+ self._add_end_tag_to_body(details["tag"])
+ if tag == "body":
+ self._in_body = False
+
+ def handle_comment(self, _data: str) -> None:
+ # ignore
+ pass
+
+ def handle_data(self, data: str) -> None:
+ if self._in_body:
+ self._add_to_body(html.escape(data, quote=False))
+ elif self._in_context(["html", "head", "title"]):
+ self._title += data
+
+ def handle_decl(self, decl: str) -> None:
+ if decl != "DOCTYPE html" or not self._in_context([]):
+ raise self._error("Unexpected declaration")
+
+ def handle_pi(self, _data: str) -> None:
+ raise self._error("Unexpected processing instruction")
+
+ def unknown_decl(self, _data: str) -> None:
+ raise self._error("Unknown declaration")
+
+ ## Public methods.
+
+ def process(self) -> tuple[str, dict[str, str]]:
+ """
+ Process the HTML file.
+
+ :returns: A 2-tuple of the filtered HTML content and a dictionary
+ that maps from the name of an asset in the HTML content to its
+ original file path.
+ """
+ with open(self._html_path, encoding="utf-8") as file:
+ self.feed(file.read())
+
+ # Make sure we are not in an invalid state.
+ if self._ancestor_details or self._in_body:
+ raise self._error("Document was not closed")
+ if not self._visited_body:
+ raise self._error("Missing a body element")
+ if not self._lang or not self._dir:
+ raise self._error("Missing lang or dir")
+ if not self._title:
+ raise self._error("Missing a title")
+
+ # Make sure all our internal references actually point to an element.
+ for pos, href in self._internal_hrefs:
+ el_id = href[1:] # Strip the starting '#'.
+ if el_id not in self._all_ids:
+ raise self._error(f"Missing an element with the id {el_id}", pos=pos)
+
+ lang = html.escape(self._lang, quote=True)
+ dir_val = html.escape(self._dir, quote=True)
+ csp = html.escape(self._CSP, quote=True)
+ js_chrome = html.escape(self._CHROME_JS, quote=True)
+ title_content = html.escape(self._title, quote=False)
+
+ html_output = f"""<!DOCTYPE html>
+<html lang="{lang}" dir="{dir_val}">
+<head>
+ <meta http-equiv="Content-Security-Policy" content="{csp}" />
+ <title>{title_content}</title>
+ <script src="{js_chrome}"></script>
+"""
+ for href in self._style_hrefs:
+ html_output += (
+ f' <link rel="stylesheet" href="{html.escape(href, quote=True)}" />\n'
+ )
+ html_output += "</head>\n"
+ html_output += self._body_content
+ html_output += "\n</html>\n"
+
+ return html_output, self._asset_paths.copy()
+
+
+def main(top_dir: str, out_dir: str, out_locales: str) -> None:
+ out_dir = os.path.abspath(out_dir)
+ out_locales = os.path.abspath(out_locales)
+ top_dir = os.path.abspath(top_dir)
+
+ if not os.path.isdir(out_dir):
+ raise ValueError(f"Not a directory: {out_dir}")
+
+ default_path = os.path.join(top_dir, ORIG_HTML_PATH)
+ if not os.path.isfile(default_path):
+ raise ValueError(f"Missing file: {default_path}")
+
+ locale_regex = re.compile("^[a-z]{2}(-[A-Z]{2})?$")
+ locale_html = {DEFAULT_LOCALE: default_path}
+ for maybe_locale in os.listdir(top_dir):
+ if not locale_regex.fullmatch(maybe_locale):
+ continue
+ maybe_path = os.path.join(top_dir, maybe_locale, ORIG_HTML_PATH)
+ if os.path.isfile(maybe_path):
+ locale_html[maybe_locale] = maybe_path
+
+ all_locales = sorted(locale_html.keys())
+ all_asset_paths: dict[str, str] = {}
+
+ for locale, html_path in locale_html.items():
+ parser = ManualParser(
+ locale=locale,
+ html_path=html_path,
+ top_dir=top_dir,
+ all_locales=all_locales,
+ )
+ content, asset_paths = parser.process()
+
+ for asset_name, orig_path in asset_paths.items():
+ if asset_name not in all_asset_paths:
+ all_asset_paths[asset_name] = orig_path
+ elif all_asset_paths[asset_name] != orig_path:
+ raise ValueError(f"Duplicate asset names: {asset_name}")
+
+ out_html_path = os.path.join(out_dir, HTML_NAME_TEMPLATE.replace("#", locale))
+ with open(out_html_path, "w", encoding="utf-8") as file:
+ file.write(content)
+
+ asset_dir = os.path.join(out_dir, ASSETS_DIR_NAME)
+ try:
+ os.mkdir(asset_dir)
+ except FileExistsError:
+ pass
+
+ for asset_name, orig_path in all_asset_paths.items():
+ shutil.copyfile(orig_path, os.path.join(os.path.join(asset_dir, asset_name)))
+
+ with open(out_locales, "w", encoding="utf-8") as file:
+ file.write(",".join(all_locales))
+
+
+if __name__ == "__main__":
+ import argparse
+
+ arg_parser = argparse.ArgumentParser(
+ description="Filter the offline manual HTML files to be used in Tor Browser"
+ )
+
+ arg_parser.add_argument(
+ "--in-dir",
+ required=True,
+ help="The 'public' directory to read the original HTML files from",
+ )
+ arg_parser.add_argument(
+ "--out-dir",
+ required=True,
+ help="The directory to write the output HTML and assets to",
+ )
+ arg_parser.add_argument(
+ "--out-locales",
+ required=True,
+ help="The file to write the list of locales to",
+ )
+
+ args = arg_parser.parse_args()
+ main(args.in_dir, args.out_dir, args.out_locales)
=====================================
projects/manual/test_package_marble_build_artifacts.py
=====================================
@@ -0,0 +1,1654 @@
+import os
+import re
+import shutil
+import tempfile
+import textwrap
+import unittest
+
+from package_marble_build_artifacts import ManualParser, main
+
+EXPECT_CSP_META = '<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src chrome:; img-src chrome:; script-src chrome:" />'
+EXPECT_SCRIPT = (
+ '<script src="chrome://browser/content/aboutmanual/aboutManual.js"></script>'
+)
+EXPECT_CHROME_ASSETS = "chrome://browser/content/aboutmanual/assets"
+
+
+class TestSingleHTML(unittest.TestCase):
+ top_dir: str | None = None
+ html_path: str | None = None
+ all_locales = ["en", "ar"]
+
+ # Set the TestCase.maxDiff to a larger number to see the full HTML.
+ maxDiff = 2000
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ cls.top_dir = tempfile.mkdtemp()
+ try:
+ os.chdir(cls.top_dir)
+ html_dir = os.path.join(cls.top_dir, "html/html")
+ assets1_dir = os.path.join(cls.top_dir, "html/assets1")
+ assets2_sub_dir = os.path.join(cls.top_dir, "assets2/sub")
+ os.makedirs(html_dir)
+ os.makedirs(assets1_dir)
+ os.makedirs(assets2_sub_dir)
+ cls.html_path = os.path.join(html_dir, "test.html")
+
+ for filename in (
+ "html/html/neighbour1.svg",
+ "html/html/neighbour2.svg",
+ "html/html/neighbour1.css",
+ "html/html/neighbour2.css",
+ "html/assets1/image1.png",
+ "html/assets1/style1.css",
+ "assets2/image2.png",
+ "assets2/style2.css",
+ "assets2/sub/image2.png",
+ "assets2/sub/style2.css",
+ "html/assets1/image(a)2x.png",
+ "html/assets1/🦭.svg",
+ "html/assets1/~.svg",
+ ):
+ # Create an empty file.
+ open(os.path.join(cls.top_dir, filename), "w", encoding="utf-8").close()
+ except:
+ shutil.rmtree(cls.top_dir)
+ raise
+
+ @classmethod
+ def tearDownClass(cls) -> None:
+ assert cls.top_dir is not None
+ shutil.rmtree(cls.top_dir)
+
+ @staticmethod
+ def dedent(in_str: str) -> str:
+ out_str = textwrap.dedent(in_str)
+ if out_str.startswith("\n"):
+ out_str = out_str[1:]
+ return out_str
+
+ def assert_html_out(
+ self, in_html: str, locale: str, expect_html: str, expect_assets: dict[str, str]
+ ) -> None:
+ assert self.html_path is not None
+ assert self.top_dir is not None
+
+ with open(self.html_path, "w", encoding="utf-8") as file:
+ file.write(self.dedent(in_html))
+ expect_html = self.dedent(expect_html)
+
+ expect_assets = {
+ key: os.path.join(self.top_dir, val) for key, val in expect_assets.items()
+ }
+
+ parser = ManualParser(
+ locale=locale,
+ html_path=self.html_path,
+ top_dir=self.top_dir,
+ all_locales=self.all_locales,
+ )
+ out_html, out_assets = parser.process()
+ self.assertEqual(out_html, expect_html, "HTML should match")
+ self.assertEqual(out_assets, expect_assets)
+
+ def assert_body_out(
+ self,
+ in_body: str,
+ expect_body: str,
+ expect_assets: dict[str, str],
+ locale: str = "ar",
+ ) -> None:
+ indent = " "
+ in_body = textwrap.indent(self.dedent(in_body), prefix=indent)
+ expect_body = textwrap.indent(self.dedent(expect_body), prefix=indent)
+ in_html = f"""
+ <html lang="{locale}" dir="rtl">
+ <head>
+ <title>Test</title>
+ </head>\n{in_body}{indent}</html>
+ """
+ expect_html = f"""
+ <!DOCTYPE html>
+ <html lang="{locale}" dir="rtl">
+ <head>
+ {EXPECT_CSP_META}
+ <title>Test</title>
+ {EXPECT_SCRIPT}
+ </head>\n{expect_body}{indent}</html>
+ """
+ self.assert_html_out(in_html, locale, expect_html, expect_assets)
+
+ def assert_html_raises(
+ self, in_html: str, locale: str, expect_err_str: str
+ ) -> None:
+ assert self.html_path is not None
+ assert self.top_dir is not None
+
+ with open(self.html_path, "w", encoding="utf-8") as file:
+ file.write(self.dedent(in_html))
+ parser = ManualParser(
+ locale=locale,
+ html_path=self.html_path,
+ top_dir=self.top_dir,
+ all_locales=self.all_locales,
+ )
+ with self.assertRaisesRegex(
+ ValueError,
+ r"^[^:]+.html: \([0-9]+, [0-9]+\): " + re.escape(expect_err_str) + "$",
+ ):
+ parser.process()
+
+ def assert_body_raises(self, in_body: str, expect_err_str: str) -> None:
+ indent = " "
+ in_body = textwrap.indent(self.dedent(in_body), prefix=indent)
+ in_html = f"""
+ <html lang="ar" dir="rtl">
+ <head>
+ <title>Test</title>
+ </head>\n{in_body}{indent}</html>
+ """
+ self.assert_html_raises(in_html, "ar", expect_err_str)
+
+ def test_empty_body(self) -> None:
+ self.assert_body_out("<body></body>\n", "<body></body>\n", {})
+
+ def test_html_tag(self) -> None:
+ self.assert_html_out(
+ """
+ <html lang="en" dir="rtl">
+ <head>
+ <title>Test</title>
+ </head>
+ <body>
+ </body>
+ </html>
+ """,
+ "en",
+ f"""
+ <!DOCTYPE html>
+ <html lang="en" dir="rtl">
+ <head>
+ {EXPECT_CSP_META}
+ <title>Test</title>
+ {EXPECT_SCRIPT}
+ </head>
+ <body>
+ </body>
+ </html>
+ """,
+ {},
+ )
+ # Invalid.
+ self.assert_html_raises(
+ """
+ <div>
+ <html>
+ </html>
+ </div>
+ """,
+ "ar",
+ "html has a parent",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ </html>
+ <html lang="en" dir="ltr">
+ </html>
+ """,
+ "ar",
+ "More than one html tag",
+ )
+ self.assert_html_raises(
+ """
+ <html dir="rtl">
+ </html>
+ """,
+ "ar",
+ "html is missing a lang attribute",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar">
+ </html>
+ """,
+ "ar",
+ "html is missing a dir attribute",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="en" dir="rtl">
+ </html>
+ """,
+ "ar",
+ "Unexpected lang attribute: en",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="en" dir="LTR">
+ </html>
+ """,
+ "en",
+ "Unexpected dir attribute: LTR",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="en" dir="auto">
+ </html>
+ """,
+ "en",
+ "Unexpected dir attribute: auto",
+ )
+
+ def test_head_ignored(self) -> None:
+ # Most of the tags and attributes in the head are ignored.
+ self.assert_html_out(
+ """
+ <html lang="ar" dir="rtl" other="blah">
+ <head attr="ignored">
+ <meta charset="utf-8">
+ <meta content="width=device-width, initial-scale=1.0" name="viewport" />
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
+ <link rel="stylesheet" href="../assets1/style1.css" other="blah">
+ <link rel="other" href="https://example.org">
+ <title some-attr="blah">دليل استخدام متصفح تور</title>
+ <script src="https://example.org"></script>
+ <script src="../script.js"></script>
+ <script>
+ const val = "hello";
+ </script>
+ <div>oops</div>
+ <style>
+ body {
+ display: none;
+ }
+ </style>
+ </head>
+ <div>oops</div>
+ <body>
+ </body>
+ </html>
+ """,
+ "ar",
+ f"""
+ <!DOCTYPE html>
+ <html lang="ar" dir="rtl">
+ <head>
+ {EXPECT_CSP_META}
+ <title>دليل استخدام متصفح تور</title>
+ {EXPECT_SCRIPT}
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/html__assets1__style1.css" />
+ </head>
+ <body>
+ </body>
+ </html>
+ """,
+ {"html__assets1__style1.css": "html/assets1/style1.css"},
+ )
+
+ def test_title(self) -> None:
+ # NOTE: Expected to fail in python 3.13.5, 3.12.11, 3.11.12, 3.10.18,
+ # 3.9.22 and earlier, due to a bug in html.parser, which fails to treat
+ # the "<div>" as text content. This should not cause any issues in
+ # practice, since the title is not expected include a raw <.
+ # It is tested here for robustness.
+ self.assert_html_out(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <title>title content <div> 'more"</title>
+ </head>
+ <body>
+ </body>
+ </html>
+ """,
+ "ar",
+ f"""
+ <!DOCTYPE html>
+ <html lang="ar" dir="rtl">
+ <head>
+ {EXPECT_CSP_META}
+ <title>title content <div> 'more"</title>
+ {EXPECT_SCRIPT}
+ </head>
+ <body>
+ </body>
+ </html>
+ """,
+ {},
+ )
+ # <title> elements outside the <head> are ignored.
+ self.assert_html_out(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <title>Test</title>
+ </head>
+ <title>Some other title</title>
+ <body>
+ <title>Body title</title>
+ </body>
+ </html>
+ """,
+ "ar",
+ f"""
+ <!DOCTYPE html>
+ <html lang="ar" dir="rtl">
+ <head>
+ {EXPECT_CSP_META}
+ <title>Test</title>
+ {EXPECT_SCRIPT}
+ </head>
+ <body>
+ <title>Body title</title>
+ </body>
+ </html>
+ """,
+ {},
+ )
+
+ def test_stylesheets(self) -> None:
+ self.assert_html_out(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="../assets1/style1.css">
+ <link rel="stylesheet" href="../../assets2/sub/style2.css?query=ignored">
+ <link rel="stylesheet" href="/assets2/style2.css#anchor-ignored">
+ <link rel="stylesheet" href="neighbour1.css">
+ <link rel="stylesheet" href="./neighbour2.css">
+ <title>دليل استخدام متصفح تور</title>
+ </head>
+ <body>
+ </body>
+ </html>
+ """,
+ "ar",
+ f"""
+ <!DOCTYPE html>
+ <html lang="ar" dir="rtl">
+ <head>
+ {EXPECT_CSP_META}
+ <title>دليل استخدام متصفح تور</title>
+ {EXPECT_SCRIPT}
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/html__assets1__style1.css" />
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/assets2__sub__style2.css" />
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/assets2__style2.css" />
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/html__html__neighbour1.css" />
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/html__html__neighbour2.css" />
+ </head>
+ <body>
+ </body>
+ </html>
+ """,
+ {
+ "html__assets1__style1.css": "html/assets1/style1.css",
+ "assets2__sub__style2.css": "assets2/sub/style2.css",
+ "assets2__style2.css": "assets2/style2.css",
+ "html__html__neighbour1.css": "html/html/neighbour1.css",
+ "html__html__neighbour2.css": "html/html/neighbour2.css",
+ },
+ )
+
+ # Stylesheets and img references are treated the same.
+ self.assert_html_out(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="../assets1/style1.css">
+ <link rel="stylesheet" href="/assets2/sub/style2.css">
+ <link rel="stylesheet" href="neighbour2.css">
+ <title>دليل استخدام متصفح تور</title>
+ </head>
+ <body>
+ <img src="../assets1/image1.png">
+ <img src="/assets2/image2.png">
+ <img src="neighbour2.svg">
+ </body>
+ </html>
+ """,
+ "ar",
+ f"""
+ <!DOCTYPE html>
+ <html lang="ar" dir="rtl">
+ <head>
+ {EXPECT_CSP_META}
+ <title>دليل استخدام متصفح تور</title>
+ {EXPECT_SCRIPT}
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/html__assets1__style1.css" />
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/assets2__sub__style2.css" />
+ <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/html__html__neighbour2.css" />
+ </head>
+ <body>
+ <img src="{EXPECT_CHROME_ASSETS}/html__assets1__image1.png" />
+ <img src="{EXPECT_CHROME_ASSETS}/assets2__image2.png" />
+ <img src="{EXPECT_CHROME_ASSETS}/html__html__neighbour2.svg" />
+ </body>
+ </html>
+ """,
+ {
+ "html__assets1__style1.css": "html/assets1/style1.css",
+ "assets2__sub__style2.css": "assets2/sub/style2.css",
+ "html__html__neighbour2.css": "html/html/neighbour2.css",
+ "html__assets1__image1.png": "html/assets1/image1.png",
+ "assets2__image2.png": "assets2/image2.png",
+ "html__html__neighbour2.svg": "html/html/neighbour2.svg",
+ },
+ )
+
+ # Invalid stylesheets.
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="https://example.org">
+ </head>
+ </html>
+ """,
+ "ar",
+ "Unexpected asset path with a scheme: https",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="./style.css">
+ </head>
+ </html>
+ """,
+ "ar",
+ f"References a non-existent asset: {self.top_dir}/html/html/style.css",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="style.css">
+ </head>
+ </html>
+ """,
+ "ar",
+ f"References a non-existent asset: {self.top_dir}/html/html/style.css",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="/style.css">
+ </head>
+ </html>
+ """,
+ "ar",
+ f"References a non-existent asset: {self.top_dir}/style.css",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="../../../style.css">
+ </head>
+ </html>
+ """,
+ "ar",
+ f"References an asset outside {self.top_dir}",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="/../style.css">
+ </head>
+ </html>
+ """,
+ "ar",
+ f"References an asset outside {self.top_dir}",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href="../none.css">
+ </head>
+ </html>
+ """,
+ "ar",
+ f"References a non-existent asset: {self.top_dir}/html/none.css",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet">
+ </head>
+ </html>
+ """,
+ "ar",
+ "stylesheet link missing an href",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <link rel="stylesheet" href>
+ </head>
+ </html>
+ """,
+ "ar",
+ "stylesheet link missing an href",
+ )
+
+ def test_tag_tracking(self) -> None:
+ # Convert a self-closing non-void tag into a pair of tags with no content.
+ self.assert_body_out(
+ """
+ <body>
+ <div class="ok" />
+ </body>
+ """,
+ """
+ <body>
+ <div class="ok"></div>
+ </body>
+ """,
+ {},
+ )
+ # Convert a void tag into a self-closing tag.
+ self.assert_body_out(
+ """
+ <body>
+ <img class="ok">
+ <br>
+ <hr>
+ <hr />
+ </body>
+ """,
+ """
+ <body>
+ <img class="ok" />
+ <br />
+ <hr />
+ <hr />
+ </body>
+ """,
+ {},
+ )
+
+ # Trying to close a void tag.
+ # Missing the </div>.
+ self.assert_body_raises(
+ """
+ <body>
+ <img></img>
+ </body>
+ """,
+ "Unmatched end tag: img",
+ )
+ # Missing the </div>.
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <title>Test</title>
+ <div>
+ </head>
+ <body>
+ </body>
+ </html>
+ """,
+ "ar",
+ "Unmatched end tag: head",
+ )
+ # Closing tag with no starting tag.
+ self.assert_body_raises(
+ """
+ <body>
+ </div >
+ </body>
+ """,
+ "Unmatched end tag: div",
+ )
+ # Missing the </div>
+ self.assert_body_raises(
+ """
+ <body>
+ <div>
+ </body>
+ """,
+ "Unmatched end tag: body",
+ )
+ # Missing the closing </html>
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <title>Test</title>
+ </head>
+ <body>
+ </body>
+ """,
+ "ar",
+ "Document was not closed",
+ )
+ # Closing an unmatched tag in the outer scope.
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <head>
+ <title>Test</title>
+ </head>
+ <body>
+ </body>
+ </html>
+ </div>
+ """,
+ "ar",
+ "Unmatched end tag: div",
+ )
+
+ # Duplicate attributes.
+ self.assert_body_raises(
+ """
+ <body class="ok" class="other"></body>
+ """,
+ "body has a duplicate attribute: class",
+ )
+
+ def test_misc_data(self) -> None:
+ # Comments are ignored.
+ self.assert_body_out(
+ """
+ <body>
+ <div>a<!-- hello -->b<! hello ></div></ bogus comment>
+ </body>
+ """,
+ """
+ <body>
+ <div>ab</div>
+ </body>
+ """,
+ {},
+ )
+ # Attributes and content are escaped.
+ self.assert_body_out(
+ """
+ <body>
+ <div empty-attr attr2="'hello" attr='"<div>inject'>x < "6"</div attr2="hello">
+ </body>
+ """,
+ """
+ <body>
+ <div empty-attr attr2="'hello" attr=""<div>inject">x < "6"</div>
+ </body>
+ """,
+ {},
+ )
+
+ # CDATA.
+ self.assert_body_raises(
+ """
+ <body>
+ <div>
+ <![CDATA[<img class="ok">]]>
+ </div>
+ </body>
+ """,
+ "Unknown declaration",
+ )
+
+ # A doctype html at the start is ok.
+ self.assert_html_out(
+ """
+ <!DOCTYPE html>
+ <html lang="ar" dir="rtl">
+ <head>
+ <title>Test</title>
+ </head>
+ <body></body>
+ </html>
+ """,
+ "ar",
+ f"""
+ <!DOCTYPE html>
+ <html lang="ar" dir="rtl">
+ <head>
+ {EXPECT_CSP_META}
+ <title>Test</title>
+ {EXPECT_SCRIPT}
+ </head>
+ <body></body>
+ </html>
+ """,
+ {},
+ )
+ # Other declarations are not ok.
+ self.assert_html_raises(
+ """
+ <!DOCTYPE other>
+ <html></html>
+ """,
+ "ar",
+ "Unexpected declaration",
+ )
+ # Declaration further down.
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <!DOCTYPE html>
+ </html>
+ """,
+ "ar",
+ "Unexpected declaration",
+ )
+ # Processing instruction.
+ self.assert_body_raises(
+ """
+ <body>
+ <?xml-stylesheet href="style.css"?>
+ </body>
+ """,
+ "Unexpected processing instruction",
+ )
+
+ def test_body_tag(self) -> None:
+ # Attributes are preserved.
+ self.assert_body_out(
+ """
+ <body class="my-class" attr="blah">
+ <div></div>
+ </body>
+ """,
+ """
+ <body class="my-class" attr="blah">
+ <div></div>
+ </body>
+ """,
+ {},
+ )
+ # Invalid.
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <div>
+ <body>
+ </body>
+ </div>
+ </html>
+ """,
+ "ar",
+ "Wrong context for the body tag",
+ )
+ self.assert_html_raises(
+ """
+ <html lang="ar" dir="rtl">
+ <body>
+ </body>
+ <div></div>
+ <body></body>
+ </html>
+ """,
+ "ar",
+ "More than one body tag",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <div>
+ <body></body>
+ </div>
+ </body>
+ """,
+ "Unexpected body tag in body",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <main>
+ <script src="inject">
+ </script>
+ </main>
+ </body>
+ """,
+ "Unexpected script tag in body",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <main>
+ <SCRIpT>const val = "inject";</SCRIpT>
+ </main>
+ </body>
+ """,
+ "Unexpected script tag in body",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
+ </body>
+ """,
+ "Unexpected meta tag in body",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <link href="inject">
+ </body>
+ """,
+ "Unexpected link tag in body",
+ )
+ self.assert_body_raises(
+ "<body><noscript></noscript></body>", "Unexpected noscript tag in body"
+ )
+ self.assert_body_raises(
+ "<body><html>content</html></body>", "Unexpected html tag in body"
+ )
+ self.assert_body_raises(
+ "<body><head></head></body>", "Unexpected head tag in body"
+ )
+
+ def test_img_tag(self) -> None:
+ self.assert_body_out(
+ """
+ <body>
+ <img src="../assets1/image1.png">
+ </body>
+ """,
+ f"""
+ <body>
+ <img src="{EXPECT_CHROME_ASSETS}/html__assets1__image1.png" />
+ </body>
+ """,
+ {"html__assets1__image1.png": "html/assets1/image1.png"},
+ )
+ # Multiple and duplicates.
+ self.assert_body_out(
+ """
+ <body>
+ <img src="../assets1/image1.png">
+
+ <div>
+ <img src="../assets1/image1.png">
+ <p>
+ <img src="../../assets2/image2.png" alt="hello"/>
+ <img src="./neighbour1.svg">
+ </p>
+ <img src="../../assets2/sub/image2.png"
+ ><br>
+ <img src="/assets2/sub/image2.png">
+ <img src="neighbour1.svg">
+ <img src="./neighbour2.svg">
+ </div>
+ </body>
+ """,
+ f"""
+ <body>
+ <img src="{EXPECT_CHROME_ASSETS}/html__assets1__image1.png" />
+
+ <div>
+ <img src="{EXPECT_CHROME_ASSETS}/html__assets1__image1.png" />
+ <p>
+ <img src="{EXPECT_CHROME_ASSETS}/assets2__image2.png" alt="hello" />
+ <img src="{EXPECT_CHROME_ASSETS}/html__html__neighbour1.svg" />
+ </p>
+ <img src="{EXPECT_CHROME_ASSETS}/assets2__sub__image2.png" /><br />
+ <img src="{EXPECT_CHROME_ASSETS}/assets2__sub__image2.png" />
+ <img src="{EXPECT_CHROME_ASSETS}/html__html__neighbour1.svg" />
+ <img src="{EXPECT_CHROME_ASSETS}/html__html__neighbour2.svg" />
+ </div>
+ </body>
+ """,
+ {
+ "html__assets1__image1.png": "html/assets1/image1.png",
+ "assets2__image2.png": "assets2/image2.png",
+ "assets2__sub__image2.png": "assets2/sub/image2.png",
+ "html__html__neighbour1.svg": "html/html/neighbour1.svg",
+ "html__html__neighbour2.svg": "html/html/neighbour2.svg",
+ },
+ )
+
+ # Invalid.
+ self.assert_body_raises(
+ """
+ <body>
+ <img src="https://example.org">
+ </body>
+ """,
+ "Unexpected asset path with a scheme: https",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <img src="./image.png">
+ </body>
+ """,
+ f"References a non-existent asset: {self.top_dir}/html/html/image.png",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <img src="image.png">
+ </body>
+ """,
+ f"References a non-existent asset: {self.top_dir}/html/html/image.png",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <img src="/image.png">
+ </body>
+ """,
+ f"References a non-existent asset: {self.top_dir}/image.png",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <img src="../../../image.css">
+ </body>
+ """,
+ f"References an asset outside {self.top_dir}",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <img src="/../image.css">
+ </body>
+ """,
+ f"References an asset outside {self.top_dir}",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <img src="../none.png">
+ </body>
+ """,
+ f"References a non-existent asset: {self.top_dir}/html/none.png",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <img srcset="../assets1/image1.png 2x, ../assets1/image1.png" />
+ </body>
+ """,
+ "Unhandled srcset attribute",
+ )
+
+ # Special characters are substituted to safe characters.
+ self.assert_body_out(
+ """
+ <body>
+ <img src="../assets1/🦭.svg">
+ </body>
+ """,
+ f"""
+ <body>
+ <img src="{EXPECT_CHROME_ASSETS}/html__assets1___.svg" />
+ </body>
+ """,
+ {"html__assets1___.svg": "html/assets1/🦭.svg"},
+ )
+ self.assert_body_out(
+ """
+ <body>
+ <img src="../assets1/~.svg">
+ <img src="../assets1/image(a)2x.png">
+ </body>
+ """,
+ f"""
+ <body>
+ <img src="{EXPECT_CHROME_ASSETS}/html__assets1___.svg" />
+ <img src="{EXPECT_CHROME_ASSETS}/html__assets1__image_2x.png" />
+ </body>
+ """,
+ {
+ "html__assets1___.svg": "html/assets1/~.svg",
+ "html__assets1__image_2x.png": "html/assets1/image(a)2x.png",
+ },
+ )
+ # Conflicting names.
+ self.assert_body_raises(
+ """
+ <body>
+ <img src="../assets1/~.svg">
+ <img src="../assets1/🦭.svg">
+ </body>
+ """,
+ "More than one asset with the same name: html__assets1___.svg",
+ )
+
+ def test_a_tag(self) -> None:
+ # Internal or no href.
+ self.assert_body_out(
+ """
+ <body>
+ <div>
+ <a href="#index">index</a>
+ <a href="#index" rel="author" class="ok">in<span>d</span>ex</a>
+ <a rel="author noreferrer">none</a>
+ </div>
+ <div id="index"></div>
+ </body>
+ """,
+ """
+ <body>
+ <div>
+ <a href="#index">index</a>
+ <a href="#index" class="ok">in<span>d</span>ex</a>
+ <a>none</a>
+ </div>
+ <div id="index"></div>
+ </body>
+ """,
+ {},
+ )
+
+ # External.
+ self.assert_body_out(
+ """
+ <body>
+ <a href="https://example.org">example</a>
+ <a href="https://example.org?query=ok" rel="noopener" class="hello">example2</a>
+ <a href="https://example.net" referrerpolicy="no-referrer" target="self">example3</a>
+ </body>
+ """,
+ """
+ <body>
+ <a href="https://example.org" target="_blank" rel="noreferrer">example</a>
+ <a href="https://example.org?query=ok" class="hello" target="_blank" rel="noreferrer">example2</a>
+ <a href="https://example.net" target="_blank" rel="noreferrer">example3</a>
+ </body>
+ """,
+ {},
+ )
+
+ # Relative: tor-browser.
+ self.assert_body_out(
+ """
+ <body>
+ <a href="../../tor-browser/sub-page/">other section</a>
+ <a href="../../tor-browser/sub-page" rel="author prev">other section2</a>
+ <div>
+ <a href="../../tor-browser/sub-page#anchor">other section3</a>
+ <a href="../../tor-browser/sub-page/further#anchor" class="ok">other section4</a>
+ </div>
+ <div id="sub-page">
+ <span id="sub-page___anchor"></span>
+ <div id="sub-page__further">
+ <span id="sub-page__further___anchor"></span>
+ </div>
+ </div>
+ </body>
+ """,
+ """
+ <body>
+ <a href="#sub-page">other section</a>
+ <a href="#sub-page">other section2</a>
+ <div>
+ <a href="#sub-page___anchor">other section3</a>
+ <a href="#sub-page__further___anchor" class="ok">other section4</a>
+ </div>
+ <div id="sub-page">
+ <span id="sub-page___anchor"></span>
+ <div id="sub-page__further">
+ <span id="sub-page__further___anchor"></span>
+ </div>
+ </div>
+ </body>
+ """,
+ {},
+ )
+ # A plain "tor-browser/" will point to "#index" as a special case.
+ self.assert_body_out(
+ """
+ <body>
+ <a href="../../tor-browser/">top</a>
+ <div id="index"></div>
+ </body>
+ """,
+ """
+ <body>
+ <a href="#index">top</a>
+ <div id="index"></div>
+ </body>
+ """,
+ {},
+ )
+ # Relative: outside tor-browser and get-in-touch.
+ self.assert_body_out(
+ """
+ <body>
+ <a href="../../tor-vpn/sub-page">Tor VPN</a>
+ <a href="../../tor-vpn/sub-page?query=ok#anchor">Tor VPN</a>
+ </body>
+ """,
+ """
+ <body>
+ <a href="https://support.torproject.org/ar/tor-vpn/sub-page" target="_blank" rel="noreferrer">Tor VPN</a>
+ <a href="https://support.torproject.org/ar/tor-vpn/sub-page#anchor" target="_blank" rel="noreferrer">Tor VPN</a>
+ </body>
+ """,
+ {},
+ locale="ar",
+ )
+ # For the "en" locale, we do not include /en/ in the support URL.
+ self.assert_body_out(
+ """
+ <body>
+ <a href="../../tor-vpn/sub-page">Tor VPN</a>
+ <a href="../../tor-vpn/sub-page?query=ok#anchor">Tor VPN</a>
+ </body>
+ """,
+ """
+ <body>
+ <a href="https://support.torproject.org/tor-vpn/sub-page" target="_blank" rel="noreferrer">Tor VPN</a>
+ <a href="https://support.torproject.org/tor-vpn/sub-page#anchor" target="_blank" rel="noreferrer">Tor VPN</a>
+ </body>
+ """,
+ {},
+ locale="en",
+ )
+ # Relative: get-in-touch.
+ # Only "bug-or-feedback" or "user-support" is expected.
+ self.assert_body_out(
+ """
+ <body>
+ <a href="../../get-in-touch/bug-or-feedback" class="hello">bug</a>
+ <a href="../../get-in-touch/user-support#anchor">support</a>
+ <a href="../../get-in-touch/other">get in touch other</a>
+ <a href="../../get-in-touch">get in touch top</a>
+ <div id="get-in-touch__bug-or-feedback"></div>
+ <div id="get-in-touch__user-support___anchor"></div>
+ </body>
+ """,
+ """
+ <body>
+ <a href="#get-in-touch__bug-or-feedback" class="hello">bug</a>
+ <a href="#get-in-touch__user-support___anchor">support</a>
+ <a href="https://support.torproject.org/ar/get-in-touch/other" target="_blank" rel="noreferrer">get in touch other</a>
+ <a href="https://support.torproject.org/ar/get-in-touch" target="_blank" rel="noreferrer">get in touch top</a>
+ <div id="get-in-touch__bug-or-feedback"></div>
+ <div id="get-in-touch__user-support___anchor"></div>
+ </body>
+ """,
+ {},
+ locale="ar",
+ )
+
+ # mailto href is removed.
+ self.assert_body_out(
+ """
+ <body>
+ <a href="mailto:me@email.org" class="ok">email</a>
+ </body>
+ """,
+ """
+ <body>
+ <a class="ok">email</a>
+ </body>
+ """,
+ {},
+ )
+
+ # Missing internal id.
+ self.assert_body_raises(
+ """
+ <body>
+ <a href="#top"></a>
+ </body>
+ """,
+ "Missing an element with the id top",
+ )
+
+ # Missing internal id.
+ self.assert_body_raises(
+ """
+ <body>
+ <a href="../../tor-browser/sub-page"></a>
+ </body>
+ """,
+ "Missing an element with the id sub-page",
+ )
+
+ # Unhandled hrefs.
+ self.assert_body_raises(
+ """
+ <body>
+ <a href="../page"></a>
+ </body>
+ """,
+ "Unexpected href: ../page",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <a href="chrome://page"></a>
+ </body>
+ """,
+ "Unexpected href: chrome://page",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <a href="../../../page"></a>
+ </body>
+ """,
+ "Unexpected path: ../../../page",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <a href="../../page/./"></a>
+ </body>
+ """,
+ "Unexpected path: ../../page/./",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <a href="../../page/../other"></a>
+ </body>
+ """,
+ "Unexpected path: ../../page/../other",
+ )
+
+ def test_ids(self) -> None:
+ # The "id" of the heading-anchor adopts a prefix from the nearest
+ # olm-page.
+ self.assert_body_out(
+ """
+ <body>
+ <div class="other olm-page" id="somesection">
+ <main>
+ <div id="somename" class="heading-anchor other"></div>
+ <h2>Some heading</h2>
+ </main>
+ </div>
+ </body>
+ """,
+ """
+ <body>
+ <div class="other olm-page" id="somesection">
+ <main>
+ <div id="somesection___somename" class="heading-anchor other"></div>
+ <h2>Some heading</h2>
+ </main>
+ </div>
+ </body>
+ """,
+ {},
+ )
+
+ # Multiple.
+ self.assert_body_out(
+ """
+ <body>
+ <div class="other olm-page" id="somesection">
+ <main>
+ <div id="somename" class="heading-anchor other"></div>
+ <h2>Some heading</h2>
+ </main>
+ </div>
+ <div>
+ <div class="olm-page" id="somesection__2">
+ <main>
+ <div>
+ <div id="somename2" class="heading-anchor"></div>
+ <h2>Some heading</h2>
+ </div>
+ </main>
+ </div>
+ </div>
+ </body>
+ """,
+ """
+ <body>
+ <div class="other olm-page" id="somesection">
+ <main>
+ <div id="somesection___somename" class="heading-anchor other"></div>
+ <h2>Some heading</h2>
+ </main>
+ </div>
+ <div>
+ <div class="olm-page" id="somesection__2">
+ <main>
+ <div>
+ <div id="somesection__2___somename2" class="heading-anchor"></div>
+ <h2>Some heading</h2>
+ </div>
+ </main>
+ </div>
+ </div>
+ </body>
+ """,
+ {},
+ )
+
+ # Duplicate ids.
+ self.assert_body_raises(
+ """
+ <body>
+ <div id="first"></div>
+ <div id="first"></div>
+ </body>
+ """,
+ "Duplicate id: first",
+ )
+ self.assert_body_raises(
+ """
+ <body>
+ <div id="somesection___somename"></div>
+ <div class="olm-page" id="somesection">
+ <div id="somename" class="heading-anchor"></div>
+ </div>
+ </body>
+ """,
+ "Duplicate id: somesection___somename",
+ )
+
+ # Ignore duplicate ids on a toggler element.
+ # TODO: tor-browser-build#41818. Remove this part of the test.
+ self.assert_body_out(
+ """
+ <body>
+ <div id="first"></div>
+ <input id="first" class="toggler">
+ </body>
+ """,
+ """
+ <body>
+ <div id="first"></div>
+ <input class="toggler" />
+ </body>
+ """,
+ {},
+ )
+
+ # Nested olm-page.
+ self.assert_body_raises(
+ """
+ <body>
+ <div class="olm-page" id="outer">
+ <main>
+ <div class="olm-page" id="outer__sub"></div>
+ </main>
+ </div>
+ </body>
+ """,
+ "olm-page is below another",
+ )
+
+ # olm-page with no id.
+ self.assert_body_raises(
+ """
+ <body>
+ <div class="olm-page"></div>
+ </body>
+ """,
+ "olm-page is missing an id",
+ )
+
+ # Missing olm-page.
+ self.assert_body_raises(
+ """
+ <body>
+ <div class="olm-page" id="ok"></div>
+ <div class="heading-anchor" id="ok2"></div>
+ </body>
+ """,
+ "Missing a page to use for the heading id",
+ )
+
+ # heading-anchor with no id.
+ self.assert_body_raises(
+ """
+ <body>
+ <div class="olm-page" id="ok">
+ <div class="heading-anchor"></div>
+ </div>
+ </body>
+ """,
+ "Heading is missing an id",
+ )
+
+
+class TestMain(unittest.TestCase):
+ # Set the TestCase.maxDiff to a larger number to see the full HTML.
+ maxDiff = 2000
+
+ def setUp(self) -> None:
+ self.root_dir = tempfile.mkdtemp()
+ try:
+ os.chdir(self.root_dir)
+ self.public_dir = os.path.join(self.root_dir, "public")
+ self.out_dir = os.path.join(self.root_dir, "output")
+ self.out_locales = os.path.join(self.root_dir, "locales/available")
+ os.mkdir(self.public_dir)
+ os.mkdir(self.out_dir)
+ os.mkdir(os.path.dirname(self.out_locales))
+ except:
+ shutil.rmtree(self.root_dir)
+ raise
+
+ def tearDown(self) -> None:
+ shutil.rmtree(self.root_dir)
+
+ def assert_out_content(self, path: str, expect_content: str) -> None:
+ with open(os.path.join(self.out_dir, path), encoding="utf-8") as file:
+ self.assertEqual(file.read(), expect_content)
+
+ def test_locales(self) -> None:
+ base = "offline/tor-browser/index.html"
+ for locale, rel_path in (
+ ("en", base),
+ ("ar", f"ar/{base}"),
+ ("zh-CN", f"zh-CN/{base}"),
+ # Locales with the wrong lang tags should be ignored.
+ ("inv", f"inv/{base}"),
+ ("in-VAL", f"in-VAL/{base}"),
+ (",", f",/{base}"),
+ ):
+ path = os.path.join(self.public_dir, rel_path)
+ os.makedirs(os.path.dirname(path))
+ with open(path, "w", encoding="utf-8") as file:
+ file.write(
+ "<!DOCTYPE html>"
+ f'<html lang={locale} dir="rtl">'
+ f"<head><title>{locale} manual</title></head><body>"
+ '<a href="../../other">content</a>'
+ '<a href="../../tor-browser/sub-page"></a><div id="sub-page"></div>'
+ "</body></html>"
+ )
+ # Directories with the correct code, but no index.html file in the
+ # expected place are ignored.
+ os.makedirs(os.path.join(self.public_dir, "js"))
+ os.makedirs(os.path.join(self.public_dir, "de/offline/tor-browser"))
+ # File in wrong place:
+ open(
+ os.path.join(self.public_dir, "de/offline/index.html"),
+ "w",
+ encoding="utf-8",
+ ).close()
+ # Not a file:
+ os.makedirs(os.path.join(self.public_dir, f"bb/{base}"))
+
+ main(self.public_dir, self.out_dir, self.out_locales)
+
+ self.assertCountEqual(
+ os.listdir(self.out_dir),
+ [
+ "assets",
+ "aboutManual-en.html",
+ "aboutManual-ar.html",
+ "aboutManual-zh-CN.html",
+ ],
+ )
+ self.assertEqual(os.listdir(os.path.join(self.out_dir, "assets")), [])
+
+ with open(self.out_locales, encoding="utf-8") as file:
+ self.assertEqual(file.read(), "ar,en,zh-CN", "list matches")
+
+ for locale, is_default in (("en", True), ("ar", False), ("zh-CN", False)):
+ filename = f"aboutManual-{locale}.html"
+ support_page = (
+ "https://support.torproject.org/other"
+ if is_default
+ else f"https://support.torproject.org/{locale}/other"
+ )
+ self.assert_out_content(
+ filename,
+ "<!DOCTYPE html>\n"
+ f'<html lang="{locale}" dir="rtl">\n<head>\n'
+ f" {EXPECT_CSP_META}\n"
+ f" <title>{locale} manual</title>\n"
+ f" {EXPECT_SCRIPT}\n"
+ "</head>\n<body>"
+ f'<a href="{support_page}" target="_blank" rel="noreferrer">content</a>'
+ '<a href="#sub-page"></a><div id="sub-page"></div>'
+ "</body>\n</html>\n",
+ )
+
+ # Missing default locale's HTML.
+ default_html = os.path.join(self.public_dir, base)
+ os.unlink(default_html)
+ with self.assertRaisesRegex(
+ ValueError, rf"Missing file: {re.escape(default_html)}"
+ ):
+ main(self.public_dir, self.out_dir, self.out_locales)
+
+ def test_assets(self) -> None:
+ def write_html(
+ path: str, locale: str, style1: str, style2: str, image1: str, image2: str
+ ) -> None:
+ path = os.path.join(self.public_dir, path)
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, "w", encoding="utf-8") as file:
+ file.write(
+ "<!DOCTYPE html>\n"
+ f'<html lang={locale} dir="rtl">\n'
+ "<head>"
+ f'<link rel="stylesheet" href={style1}>'
+ "<title>Test</title>"
+ f'<link rel="stylesheet" href={style2}>'
+ "</head><body>"
+ f'<img src="{image1}"><img src="{image2}">'
+ "</body></html>"
+ )
+
+ for rel_path, content in (
+ ("offline/ltr.min.css", "0"),
+ ("offline/rtl.min.css", "1"),
+ ("static/style.css", "2"),
+ ("offline/image.png", "3"),
+ ("ar/offline/image.png", "4"),
+ ("sub/page/image(a)2x.png", "5"),
+ ("sub/page/image~2x.png", "6"),
+ ):
+ orig_path = os.path.join(self.public_dir, rel_path)
+ os.makedirs(os.path.dirname(orig_path), exist_ok=True)
+ with open(orig_path, "w", encoding="utf-8") as file:
+ file.write(content)
+
+ write_html(
+ "offline/tor-browser/index.html",
+ "en",
+ "../ltr.min.css",
+ "../../static/style.css",
+ "../image.png",
+ "../../sub/page/image(a)2x.png",
+ )
+ write_html(
+ "ar/offline/tor-browser/index.html",
+ "ar",
+ "../../../offline/rtl.min.css",
+ "../../../static/style.css",
+ "../image.png",
+ "../../../sub/page/image(a)2x.png",
+ )
+
+ main(self.public_dir, self.out_dir, self.out_locales)
+
+ self.assertCountEqual(
+ os.listdir(self.out_dir),
+ ["assets", "aboutManual-en.html", "aboutManual-ar.html"],
+ )
+ self.assertCountEqual(
+ os.listdir(os.path.join(self.out_dir, "assets")),
+ [
+ "offline__ltr.min.css",
+ "offline__rtl.min.css",
+ "static__style.css",
+ "offline__image.png",
+ "ar__offline__image.png",
+ "sub__page__image_2x.png",
+ ],
+ )
+
+ self.assert_out_content(
+ "aboutManual-en.html",
+ "<!DOCTYPE html>\n"
+ '<html lang="en" dir="rtl">\n'
+ "<head>\n"
+ f" {EXPECT_CSP_META}\n"
+ " <title>Test</title>\n"
+ f" {EXPECT_SCRIPT}\n"
+ f' <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/offline__ltr.min.css" />\n'
+ f' <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/static__style.css" />\n'
+ "</head>\n<body>"
+ f'<img src="{EXPECT_CHROME_ASSETS}/offline__image.png" />'
+ f'<img src="{EXPECT_CHROME_ASSETS}/sub__page__image_2x.png" />'
+ "</body>\n</html>\n",
+ )
+
+ self.assert_out_content(
+ "aboutManual-ar.html",
+ "<!DOCTYPE html>\n"
+ '<html lang="ar" dir="rtl">\n'
+ "<head>\n"
+ f" {EXPECT_CSP_META}\n"
+ " <title>Test</title>\n"
+ f" {EXPECT_SCRIPT}\n"
+ f' <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/offline__rtl.min.css" />\n'
+ f' <link rel="stylesheet" href="{EXPECT_CHROME_ASSETS}/static__style.css" />\n'
+ "</head>\n<body>"
+ f'<img src="{EXPECT_CHROME_ASSETS}/ar__offline__image.png" />'
+ f'<img src="{EXPECT_CHROME_ASSETS}/sub__page__image_2x.png" />'
+ "</body>\n</html>\n",
+ )
+
+ # Make sure the new assets were copied over.
+ self.assert_out_content("assets/offline__ltr.min.css", "0")
+ self.assert_out_content("assets/offline__rtl.min.css", "1")
+ self.assert_out_content("assets/static__style.css", "2")
+ self.assert_out_content("assets/offline__image.png", "3")
+ self.assert_out_content("assets/ar__offline__image.png", "4")
+ self.assert_out_content("assets/sub__page__image_2x.png", "5")
+
+ # Duplicate asset names, from different files.
+ write_html(
+ "offline/tor-browser/index.html",
+ "en",
+ "../ltr.min.css",
+ "../../static/style.css",
+ "../image.png",
+ "../../sub/page/image~2x.png",
+ )
+ with self.assertRaisesRegex(
+ ValueError, r"^Duplicate asset names: sub__page__image_2x.png$"
+ ):
+ main(self.public_dir, self.out_dir, self.out_locales)
+
+ # Wrong relative path.
+ write_html(
+ "ar/offline/tor-browser/index.html",
+ "ar",
+ "../../../offline/rtl.min.css",
+ # Wrong relative path for the "ar" directory:
+ "../static/style.css",
+ "../image.png",
+ "../../../sub/page/image~2x.png",
+ )
+ with self.assertRaisesRegex(
+ ValueError,
+ r"^.*: References a non-existent asset: .*/ar/offline/static/style\.css$",
+ ):
+ main(self.public_dir, self.out_dir, self.out_locales)
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/f…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/f…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0